using System;
using System.Collections.Generic;
using System.Data;
using System.Drawing;
using System.Windows.Forms;

namespace DbView
{
	public delegate void CancelDelegate();

	/// <summary>
	/// Summary description for Form1.
	/// </summary>
	partial class MainForm : System.Windows.Forms.Form, IMainView, IFieldSelecterView, ITabObserver
	{
		const int MENU_HISTORY_LIMIT = 30; // max number of items to display in menu history drop

		public MainForm(String initialConnectionTag)
		{
			//
			// Required for Windows Form Designer support
			//
			InitializeComponent();

			// create the one and only presenter for the field selector pane
			this.fieldSelectorPresenter = new FieldSelectorPresenter();
			// Introduce the main form the to field selector presenter.
			this.fieldSelectorPresenter.AttachView(this);

			// apply tab strip workaround. See the special class comments
			// this.TabHost.Renderer = new ToolStripRendererWorkaround(); // don't like it
			this.TabHost.BackColor = System.Drawing.Color.Black;

			this.tabs = new TabCollection(this.TabHost);
			// Keep an eye on these indexes. If more buttons are added to the toolbar they may shift
			this.tabs.SetTabCloseImages(this.tbarImages.Images[14], this.tbarImages.Images[15]);
			this.tabs.SetVisibilityMode(TabCollection.Visibility.one); // show tabs when one is selected.
			//m_Tabs.SetTabCloseImage(this.tbarImages.Images[14]);
			// observe tab activity
			this.tabs.SetObserver(this);

			// Try a single-grid view approach
			this.currentGrid = MakeNewGridView();
			this.gridHost.Controls.Add(this.currentGrid);

			// run this after querying user settings when they are implemented.
			SetInitialTabMode();
			SetInitialFastFindMode(); // copy tab mode

			this.initialConnectionTag = initialConnectionTag;
			InitialiseMostRecentUsed();
			// If you are tracing the code more initialisation happens 
			// when the form is shown in MainFrame_Shown() below
		}

		private void SetInitialTabMode()
		{
			// get from saved settings (if any)
			string settingsTabMode = DbView.Resources.SettingsMgr.Instance.GetValue
						(DbView.Resources.ConfigDoc.CFGELM_NS_UI,
						 DbView.Resources.ConfigDoc.CFGSETTING_TABMODE);
			// find and mode from the enum
			// if not found remains as defaut set in startup
			foreach (TabMode tm in Enum.GetValues(typeof(TabMode)))
			{
				if (tm.ToString() == settingsTabMode) this.currentTabMode = tm;
			}
			// do the remaining housekeeping on changing/setting the tab mode.
			OnUpdateTabMode();
		}

		private void SetInitialFastFindMode()
		{
			// get from saved settings (if any)
			string settingsFastFindMode = DbView.Resources.SettingsMgr.Instance.GetValue
						(DbView.Resources.ConfigDoc.CFGELM_NS_UI,
						 DbView.Resources.ConfigDoc.CFGSETTING_FASTFIND);
			// if not found remains as defaut set in startup
			if (!string.IsNullOrEmpty(settingsFastFindMode))
			{
				this.FastFindActivated = settingsFastFindMode.ToUpper()[0] == 'Y';
			}
			OnUpdateFastFind();
		}

		private void InitialiseMostRecentUsed()
		{
			const string regkey_name = "SOFTWARE\\DbView";
			this.mruMenu = new MruMenuInline(fileMru, new MruMenu.ClickedHandler(OnMenuFileMru), string.Format("{0}\\MRU", regkey_name), 8);
			MruMenu mruMenu = this.mruMenu;
			Resources.ConnectionListMgr ConnectionList = Resources.ConnectionListMgr.Instance;
			// remove missing Connections
			//ConnectionListMgr cl = this.m_Model.ConnectionList;
			for (int idx = mruMenu.NumEntries - 1; idx >= 0; --idx)
			{
				string strName = mruMenu.GetFileAt(idx);
				try
				{
					// connInfo ci = cl[strName];
					// above causes mru connections from other drivers to be stripped
					//
					ConnectionInfo ci = ConnectionList.GetConnectionInfo(strName);
					if (ci == null) mruMenu.RemoveFile(strName);
				}
				catch (DbViewApplicationException)
				{
					mruMenu.RemoveFile(strName);
				}
			}
		}

		// bit hacky. Set the current connection to the most recent used so that
		// it is selected on first show of the connection dialog.
		//
		private void InitialiseConnectionfromMRU(MruMenu mruMenu)
		{
			// Hijack this to use the new schema
			ConnectionInfo ci = this.currentPresenter.Model.ConnectionList.GetLastUsedConnection();
			if (ci != null) // you would hope!
				this.currentPresenter.Model.SetConnection(ci);
			else
			{
				// fall back to old code.
				if (mruMenu.NumEntries > 0)
				{
					string strName = mruMenu.GetFileAt(0);
					// connInfo ci = cl[strName];
					// above causes crash because driver is unknown
					try
					{
						ci = this.currentPresenter.Model.ConnectionList.GetConnectionInfo(strName);
						this.currentPresenter.Model.SetConnection(ci);
					}
					catch (DbViewApplicationException)
					{
						// not sure what to do here
					}
				}
			}
		}

		//////////////////////////////////////////////////////////////////////////////////////
		// interface IMainView
		//////////////////////////////////////////////////////////////////////////////////////
		// a real application finish.
		public void Shutdown() { this.Close(); }

		public void DataFetchStarted()
		{
			this.timerConnecting.Interval = 1000; // wait a second in case the fetch is blinding fast.
			this.timerConnecting.Start();
			this.btnGo.Text = "&Cancel";
		}

		public void DataFetchFinished(bool completedOk)
		{
			this.timerConnecting.Stop();
			ColourChange(0); // restore to original
			this.btnGo.Text = "&Go";
			// can we hack in code here to cater for case where the 
			// grid has errored and left the tab in an invalid state?
			if (completedOk == false)
				CheckCloseInvalidTab();
		}

		private void CheckCloseInvalidTab()
		{
			// get active tab
			// check label and kill if new
			if (this.tabs.ActiveTab == null) return;
			string s = this.tabs.ActiveTab.Text;
			if (s.ToUpper() == "NEW")
				this.tabs.RemoveTab(this.tabs.ActiveTab);
		}

		// get a connection
		//public bool ShowConnectionDialog(ConnectionCollection connectionList) { throw new System.NotImplementedException(); }
		// show results.
		public void ResetGrid(DataTable table, bool isReadOnly, int rowIndex)
		{
			//Here is a good point at which to perform tab creation
			//appears to be obsolete: see the method code.
			CheckCreateTab(this.currentPresenter);

			// Is this a refresh?
			if (IsRefreshActivated())		// this is the production test
			// if (IsRefreshActivated() || true)  // debug refresh without being hassled by the timer
			{
				// Get existing grid table
				DataTable previous = this.currentGrid.GridDataTable;
				// Quick check that they are the same format
				if (previous != null && DataTableHelpers.HasMatchingColumns(table, previous))
				{
					// do the comparisof
					DataTableHelpers.CompareData(table, previous);
				}
			}
			// warning: isReadOnly redundant
			this.currentGrid.BindDataTable(table, !isReadOnly);
			this.currentGrid.DecorateTable(true, true); // everything please

			/*
			// turn off edit mode unless a dev database with an editable table
			// not right. Return to this later
			*/
			// assume we want edits if in dev
			bool enableEdit = this.currentPresenter.Model.DataSource.IsDevelopmentDatabase;
			// override above for read-only table
			if (isReadOnly) enableEdit = false;
			this.SetEditMode(false);

			// set row index to saved, but only if > 0 and the table has rows.
			if (rowIndex > 0 && this.currentGrid.Rows.Count > 0)
			{
				// defensive line. If refreshing a table after a deletion this can overflow.
				rowIndex = (rowIndex >= this.currentGrid.Rows.Count) ? this.currentGrid.Rows.Count - 1 : rowIndex;
				this.currentGrid.CurrentCell = this.currentGrid.Rows[rowIndex].Cells[0];
				if (this.currentGrid.Rows[rowIndex].Displayed == false) this.currentGrid.FirstDisplayedScrollingRowIndex = rowIndex;
			}
		}

		private void CheckCreateTab(MainPresenter m_CurrentPresenter)
		{
			// bootstrap
			if (this.tabs.FindUserData(this.currentPresenter) == null)
			{
				string tag = "HI";
				string text = this.currentPresenter.Model.CurrentConnection;
				this.tabs.AddTab(tag, text, this.currentPresenter, true);
			}
		}

		// filter view
		public void ApplySearchFilter(String searchText) { this.currentGrid.ApplyFilter(searchText); }
		// Detach/Attach search list so presenter can maintin the dropdown history.
		public void DetachSearchListControl() { this.comboSearchPart.DataSource = null; }
		public void AttachSearchListControl(List<String> theList, string text)
		{
			ComboBox cb = this.comboSearchPart;
			cb.DataSource = theList;
			cb.Text = text;
		}
		// Detach/Attach search list so presenter can maintin the dropdown history.
		public void DetachViewListControl() { this.comboView.DataSource = null; }
		public void AttachViewListControl(List<String> theList)
		{
			ComboBox cb = this.comboView;
			cb.DataSource = theList;
			cb.SelectedIndex = -1;
		}
		// update the view combo box item displayed
		public void ShowViewName(String viewName)
		{
			ComboBox cb = this.comboView;
			if (cb.Items.Contains(viewName))
				cb.SelectedItem = viewName;
			else
				cb.SelectedIndex = -1;
		}

		public void ResetSearchFilter(String searchText)
		{
			this.comboSearchPart.Text = searchText;
		}
		public void OnNewConnection(ConnectionInfo ci)
		{
			// get ad-hoc script
			string script = this.textCommands.Text.Trim(); //.Remove('\r').Remove('\n'); // clean the script
			if (script.Length > 0)
			{
				// check whether alread commented out.
				if (script.Substring(0, 2) == "/*" &&
					script.Substring(script.Length - 2, 2) == "*/")
					script += ""; // fake string concatenation to remove compiler warning
				else
				{
					string envelope = @"/*
	Commented out for safety on chnage of connection

{0}
*/";
					this.textCommands.Text = string.Format(envelope, this.textCommands.Text);
					this.currentPresenter.AdHocCommandTextChanged(this.textCommands.Text);

				}
			}
		}

		public void UpdateGui(ConnectionInfo ci, Commands.Cmnd command, string tableName, ViewDefinition currentView)
		{
			if (ci.Driver != this.currentDriverName)
			{
				OnChangeDriver(ci);
				this.currentDriverName = ci.Driver;
			}
			// status bar panel display
			this.statusBar1.Panels[0].Text = string.Format("{0}.{1}", ci.Server, ci.Database);
			//			this.statusBar1.Panels[1].Text = (command == Commands.cmd.TableData) ? tableName : command.ToString();
			this.statusBar1.Panels[1].Text = currentView.Name;
			string filterInfo = (command == Commands.Cmnd.TableData) ? string.Format("Filter: [ {0} ]", currentView.FilterClause) : "";
			this.statusBar1.Panels[3].Text = filterInfo;
			UpdateStatusBarFastFindCount();
			// panel 5 is a spacer to make panel 5 appear in bottom right
			this.statusBar1.Panels[6].Text = (DbView.Resources.ConfigDoc.Instance.PortableMode) ? "Portable Mode" : "";
			// update row number panel.
			DataRow dr;
			int row = this.currentGrid.GetSelectedRow(out dr);
			UpdateRowPos(row);

			// Show connection in Window caption
			SetWindowTitle(ci.Name);
			SetTabTitle(String.Format("[ {0} ] < {1} >", ci.Name, currentView.Name));

			// update Most recent used connection if applicable
			this.mruMenu.AddFile((String)ci.Name, (String)ci.Name);

			// update menu &  toolbar states (possibly only on change of command).
			SetMenuStates(command);
			SetToolBarStates(command);
			// Set the state of the field selector window.
			// ie. if not tables then off, but remember the state so
			// if back to tables then reactivate if it wa previously showing.
			SetFieldSelectorWindowState(command);

			// Speculative. Set focus on result grid
			this.currentGrid.Focus();
		}

		// separate as this is invoked frequently
		private void UpdateStatusBarFastFindCount()
		{
			string text = "";
			bool wantText = this.FastFindActivated && this.FilterText.Length > 0;
			if (wantText)
			{
				int count = this.currentGrid.CountMarked();
				// I want to show where a search matches nothing, but the other
				// search logic interferes. So blank on 0 until I think of a better way
				if (count > 0)
					text = string.Format("Found: {0} matches for {1}", count, this.FilterText);
			}
			this.statusBar1.Panels[4].Text = text;
		}

		// popup
		/*
		public void UpdatePopup(PopupConfig[] popups)
		{
			// remove pop-up from resullts.
			this.results.ContextMenu.MenuItems.Clear();
			foreach (PopupConfig puc in popups)
			{
				if (puc.Function == null)
					this.results.ContextMenu.MenuItems.Add("-");
				else
					this.results.ContextMenu.MenuItems.Add(puc.Title, new EventHandler(puc.Function));
			}
		}
*/
		public void UpdatePopup(DbViewDriver.CustomMenuItem customMenu)
		{
			// remove pop-up from resullts.
			// was commented out item, but changed to new object since discovering about sizing problems when 
			// items change.
			// this.m_CurrentGrid.ContextMenu.MenuItems.Clear();
			this.currentGrid.ContextMenu = new ContextMenu();

			AddPopupMenuItems(this.currentGrid.ContextMenu, customMenu);

		}
		void AddPopupMenuItems(ContextMenu menu, DbViewDriver.CustomMenuItem customMenu)
		{
			foreach (DbViewDriver.CustomMenuItem item in customMenu.Items)
			{
				switch (item.Type)
				{
					case DbViewDriver.CustomMenuItem.ItemType.separator:
						menu.MenuItems.Add(new MenuItem("-"));
						break;
					case DbViewDriver.CustomMenuItem.ItemType.item:
						MenuItem mi = new MenuItem(item.Name, CustomMenuItem_Click);
						mi.Tag = item;
						menu.MenuItems.Add(mi);
						break;
					case DbViewDriver.CustomMenuItem.ItemType.menu:
						// unsupported.
						MenuItem mim = new MenuItem(item.Name, CustomMenuItem_Click);
						mim.Tag = item;
						menu.MenuItems.Add(mim);
						// somehow add others
						AddPopupMenuItems(mim, item);
						break;
				}
			}
		}
		void AddPopupMenuItems(MenuItem menu, DbViewDriver.CustomMenuItem customMenu)
		{
			foreach (DbViewDriver.CustomMenuItem item in customMenu.Items)
			{
				switch (item.Type)
				{
					case DbViewDriver.CustomMenuItem.ItemType.separator:
						menu.MenuItems.Add(new MenuItem("-"));
						break;
					case DbViewDriver.CustomMenuItem.ItemType.item:
						MenuItem mi = new MenuItem(item.Name, CustomMenuItem_Click);
						mi.Tag = item;
						menu.MenuItems.Add(mi);
						break;
					case DbViewDriver.CustomMenuItem.ItemType.menu:
						// unsupported.
						MenuItem mim = new MenuItem(item.Name, CustomMenuItem_Click);
						mim.Tag = item;
						menu.MenuItems.Add(mim);
						// somehow add others
						AddPopupMenuItems(mim, item);
						break;
				}
			}
		}

		// 
		public void ShowSource(String objectName, string theSource, string searchTerm)
		{
			if (this.IsAdhocShown && this.textCommands.Text.Length == 0)
			{
				this.textCommands.Text = theSource;
				this.currentPresenter.AdHocCommandTextChanged(theSource);
			}
			else
				new SourceForm2(objectName, theSource, searchTerm).Show(); // No Parent. Makes it a tear-off window.
		}

		public bool ShowViewEditDialog(Model model, ViewDefinition viewDef)
		{
			using (ViewEditForm Dlg = new ViewEditForm(viewDef, model))
			{ return (Dlg.ShowDialog() == DialogResult.OK) ? true : false; }
		}
		public bool ShowConnectionDialog(Model model)
		{
			return model.DataSource.ConfigureConnection(model);
			/*		
						using (ConnectionEditForm2 Dlg = new ConnectionEditForm2(model))
						{	return (Dlg.ShowDialog() == DialogResult.OK) ? true : false; }
			*/
		}
		public bool ShowDriverDialog(Model model)
		{
			using (SelectDriverForm Dlg = new SelectDriverForm(model))
			{ return (Dlg.ShowDialog() == DialogResult.OK) ? true : false; }
		}
		public bool ShowViewFilterForm(ViewDefinition currentView, string fieldName, string fileValue)
		{
			using (ViewFilterForm Dlg = new ViewFilterForm(currentView, fieldName, fileValue))
			{ return (Dlg.ShowDialog() == DialogResult.OK) ? true : false; }
		}
		public bool ShowSourceSearchDialog(SearchSourceParams dialogParams)
		{
			using (SearchSourceForm Dlg = new SearchSourceForm(dialogParams))
			{ return (Dlg.ShowDialog() == DialogResult.OK) ? true : false; }
		}

		public bool ShowImportDialog(DbViewDriver.IDatabaseExportSource DbDriver, string tableName, string fileName)
		{
			using (ImportCsvForm Dlg = new ImportCsvForm(DbDriver, tableName, fileName))
			{ return (Dlg.ShowDialog() == DialogResult.OK) ? true : false; }
		}

		public bool ShowExportDialog(DbViewDriver.IDatabaseExportSource DbDriver, string tableName, string fileName, ViewDefinition viewDef)
		{
			using (ExportCsvForm Dlg = new ExportCsvForm(DbDriver, tableName, fileName, viewDef))
			{ return (Dlg.ShowDialog() == DialogResult.OK) ? true : false; }
		}

		private void toolsAdhocQueryWindow_Toggle()
		{
			if (this.IsAdhocShown)
				HideAdhoc();
			else
			{
				this.currentPresenter.UserRequestedAdhocQueryWindow();
			}
		}

		public void ShowAdhocQueryWindow(ConnectionInfo ci, DbViewDriver.IDataSource dataSource)
		{
			bool tearOffWindow = false;

			if (tearOffWindow)
			{
				AdhocQueryWindow queryWindow = new AdhocQueryWindow(ci, dataSource);
				queryWindow.Show();
			}
			else
			{
				ShowAdhoc(true);
				// move the window
				int targetWidth = this.currentGrid.DataGridWidth;
				if (targetWidth < this.Width / 2) // grid is less than 1/2 of screen
					this.GridSplitter.SplitterDistance = targetWidth;
			}

		}

		// message box. either Y/N or Y/N/Cancel - return 0 = N; 1 = Y; -1 = can.
		public int AskUser(String question, string caption, bool cancelOptionIsAvailable)
		{
			// determine the buttons depending on the cancel flag
			MessageBoxButtons buttons = (cancelOptionIsAvailable) ? MessageBoxButtons.YesNoCancel : MessageBoxButtons.YesNo;
			using (new CenterWinDialog(this))
			{
				DialogResult res = MessageBox.Show(this, question, caption, buttons, MessageBoxIcon.Question);
				// translate the reply
				return (res == DialogResult.Yes) ? 1 :
					   (res == DialogResult.No) ? 0 : -1;
			}
		}

		// message box. either info or error
		public void InformUser(String message, string caption, bool isError)
		{
			MessageBoxIcon ico = (isError) ? MessageBoxIcon.Exclamation : MessageBoxIcon.Information;
			using (new CenterWinDialog(this))
			{
				MessageBox.Show(this, message, caption, MessageBoxButtons.OK, ico);
			}
		}

		// called to reset state following a view change or commit of edit
		// now an interface method called directly by the presenter to put table data 
		// directly into edit mode when working in danger state.
		public void SetEditMode(bool editable)
		{
			// call below will onlty set to true if the grid allows it.
			editable = this.currentGrid.SetModifiable(editable);
			menuEditEdit.Checked = editable;
			// presenter owns this so now there is no need for this call (hopefully)
			// this.m_CurrentPresenter.SetEditMode(editable, this.m_CurrentGrid.GridDataTable); // register with the presenter
		}

		// This is the entry point from the menu 
		private void SetRefreshRate(MenuItem mi)
		{
			if (mi.Tag != null)
			{
				int ms;
				if (int.TryParse(mi.Tag.ToString(), out ms))
					this.currentPresenter.UserRequestedAutoRefresh(ms);
			}
		}

		// this is called by the presenter
		public void SetRefreshRate(int refreshRateMs)
		{
			// clear all menu items first.
			UncheckAllRefreshMenuItems();
			// disabling: parameter is 0 or same as previous (latter a hack for testing)
			if (refreshRateMs == 0 || refreshRateMs == this.timerRefresh.Interval)
			{
				this.timerRefresh.Enabled = false;
				this.timerRefresh.Interval = 3600 * 1000; // 1 hour!
			}
			else
			{
				this.timerRefresh.Enabled = true;
				this.timerRefresh.Interval = refreshRateMs;
				MenuItem checkItem = miFromMs(refreshRateMs);
				if (checkItem != null) checkItem.Checked = true;
			}
		}

		// return the menu item associated with the refresh rate
		private MenuItem miFromMs(int refreshRateMs)
		{
			foreach (MenuItem mi in this.actionRefreshGrid.MenuItems)
			{
				if (mi.Tag != null)
				{
					int ms;
					if (int.TryParse(mi.Tag.ToString(), out ms))
						if (ms == refreshRateMs)
							return mi;
				}
			}
			return null;
		}
		private void UncheckAllRefreshMenuItems()
		{
			foreach (MenuItem mi in this.actionRefreshGrid.MenuItems)
				mi.Checked = false;
		}
		private bool IsRefreshActivated()
		{
			foreach (MenuItem mi in this.actionRefreshGrid.MenuItems)
				if (mi.Checked) return true;
			return false;
		}

		public object WindowFrame { get { return this; } }

		public bool FinaliseEdits() { return CheckUpdateCurrentCellEdit(); }
		// end interface IMainView
		//////////////////////////////////////////////////////////////////////////////////////

		//////////////////////////////////////////////////////////////////////////////////////
		// other events
		private void MainFrame_Shown(object sender, EventArgs e)
		{
			// this.m_CurrentPresenter.MakeInitialConnection(this.m_InitialConnectionTag);
			NewTabOnStart();
			this.applicationState = Appstate.running;
		}

		private void MainForm_Load(object sender, System.EventArgs e)
		{
			HideAdhoc();
		}

		private void MainForm_FormClosing(object sender, FormClosingEventArgs e)
		{
			if (!currentPresenter.IsOkToClose())
				e.Cancel = true;
		}

		private void OnClose(object sender, System.ComponentModel.CancelEventArgs e)
		{
			// force the grid to save
			this.applicationState = Appstate.closing;
			// just kill the current edit. Hm: warning. appears not to complete the close if there is an error.
			this.currentGrid.CancelEdit();
			// persist most recent connections,
			this.mruMenu.SaveToRegistry();
			this.currentPresenter.EndSession();
		}

		/////////////////////////////////////////////////////////////////////////////        
		// All events from the previous version
		/////////////////////////////////////////////////////////////////////////////        

		/////////////////////////////////////////////////////////////////////////////        
		// menu handlers
		/////////////////////////////////////////////////////////////////////////////        

		/////////////////////////////////////////////////////////////////////////////        
		// file
		/////////////////////////////////////////////////////////////////////////////        
		private void FileConnect_Click(object sender, System.EventArgs e) { this.FileConnect(); }
		private void MenuLaunchNew_Click(object sender, EventArgs e) { this.currentPresenter.UserRequestedNewInstance(); }
		private void MenuFileSelectDatabaseSystem_Click(object sender, EventArgs e) { this.currentPresenter.UserRequestedSelectDriver(); }
		private void File_Exit_Click(object sender, System.EventArgs e) { this.currentPresenter.UserRequestedExit(); }
		private void DataChangeView_Click(object sender, System.EventArgs e) { OnEditViewDefinition(); }
		private void DataFixBadDataViews_Click(object sender, EventArgs e) { this.currentPresenter.UserRequestedFixViews(); }
		private void DataAggregate_Click(object sender, EventArgs e) { ActionAggregate(Commands.Cmnd.Aggregate); }
		private void DataSum_Click(object sender, EventArgs e) { ActionAggregate(Commands.Cmnd.Average); }
		private void DataMinMax_Click(object sender, EventArgs e) { ActionAggregate(Commands.Cmnd.MinMax); }
		private void DataCreateCustomView_Click(object sender, EventArgs e) { this.currentPresenter.UserRequestedCustomViewFromAdHoc(IsAdhocShown); }
		private void menuItem8_Click(object sender, EventArgs e) { this.currentPresenter.UserRequestManageLookups(); }
		private void EditCopyGrid_Click(object sender, System.EventArgs e) { this.OnSelectAll(); }
		private void EditExpandRows_Click(object sender, EventArgs e) { this.currentGrid.ResizeRowsToSelectedRowHeight(); }
		/////////////////////////////////////////////////////////////////////////////        
		private void SchemaTables_Click(object sender, System.EventArgs e) { this.currentPresenter.UserRequestedCommand(Commands.Cmnd.Tables, FilterText); }
		private void SchemaViews_Click(object sender, System.EventArgs e) { this.currentPresenter.UserRequestedCommand(Commands.Cmnd.Views, FilterText); }
		private void SchemaProcs_Click(object sender, System.EventArgs e) { this.currentPresenter.UserRequestedCommand(Commands.Cmnd.Procedures, FilterText); }
		private void SchemaFuncs_Click(object sender, System.EventArgs e) { this.currentPresenter.UserRequestedCommand(Commands.Cmnd.Functions, FilterText); }
		private void MenuObjectsForeignKeys_Click(object sender, EventArgs e) { this.currentPresenter.UserRequestedCommand(Commands.Cmnd.ForeignKeys, FilterText); }
		private void MenuObjectsIndexes_Click(object sender, EventArgs e) { this.currentPresenter.UserRequestedCommand(Commands.Cmnd.Indexes, FilterText); }
		private void SchemaTriggers_Click(object sender, System.EventArgs e) { this.currentPresenter.UserRequestedCommand(Commands.Cmnd.Triggers, FilterText); }
		private void SchemaCalculations_Click(object sender, EventArgs e) { this.currentPresenter.UserRequestedCommand(Commands.Cmnd.Computed, FilterText); }
		private void SchemaDependencies_Click(object sender, EventArgs e) { this.currentPresenter.UserRequestedCommand(Commands.Cmnd.Dependencies, FilterText); }
		private void SchemaProcesses_Click(object sender, EventArgs e) { DoCustomItem(sender as MenuItem); }
		private void ShowPermissions_Click(object sender, System.EventArgs e) { this.currentPresenter.UserRequestedCommand(Commands.Cmnd.Permissions, FilterText); }
		private void CustomMenuItem_Click(object sender, EventArgs e) { DoCustomItem(sender as MenuItem); }

		// Action
		private void ActionSearch_Click(object sender, System.EventArgs e) { OnSearch(FilterText); }//m_CurrentPresenter.UserRequestedSearch(FilterText); }
		private void ActionStructure_Click(object sender, System.EventArgs e) { this.currentPresenter.UserRequestedCommand(Commands.Cmnd.Fields, FilterText); }
		private void ActionData_Click(object sender, System.EventArgs e) { this.currentPresenter.UserRequestedCommand(Commands.Cmnd.TableData, FilterText); }
		private void ActionClearSearch_Click(object sender, System.EventArgs e) { ClearRestoreSearch(); }
		private void ActionClearSearchEx_Click(object sender, System.EventArgs e) { ClearRestoreSearch(); }
		private void ActionFilter_Click(object sender, System.EventArgs e) { SetFilter(); }
		private void ActionRefreshGrid_Click(object sender, EventArgs e) { SetRefreshRate(sender as MenuItem); }

		private void ActionTest_Click(object sender, System.EventArgs e)
		{
			// 
			this.currentPresenter.UserRequestedTest();
		}
		// Navigate
		// all not necessary
		// private void actionNavigateBack_Click(object sender, System.EventArgs e) { this.m_Presenter.UserRequestedBack(); }
		// private void actionNavigateForwards_Click(object sender, System.EventArgs e) { throw new System.NotImplementedException(); }
		// private void actionGoto_Click(object sender, System.EventArgs e) { throw new System.NotImplementedException(); }
		// Help
		private void HelpWhere_Click(object sender, System.EventArgs e) { this.currentPresenter.UserRequestedLocationOfConfigFile(); }
		private void HelpAbout_Click(object sender, EventArgs e)
		{
			AboutBox aboutDialog = new AboutBox();
			aboutDialog.ShowDialog(this);
		}
		private void HelpContext_Click(object sender, EventArgs e) { OnHelpContext(); }
		private void HelpOpenUserGuide_Click(object sender, EventArgs e) { this.currentPresenter.UserRequestedOpenHelp(); }

		private void ShowHistory_Click(object sender, System.EventArgs e) { this.currentPresenter.UserRequestedHistory(FilterText); }
		private void ActionSearchSource_Click(object sender, System.EventArgs e)
		{
			this.currentPresenter.UserRequestedSearchSource("");
		}

		private void ToolsFieldSelector_Click(object sender, EventArgs e)
		{
			ShowFieldSelector();
		}

		private void ShowFieldSelector()
		{
			// speculative
			this.FieldPanel.Visible = true;
			this.fieldPanelActive = true; // preserve state
			this.tbFieldSelecter.Pushed = this.FieldPanel.Visible;
			this.fieldSelectorPresenter.UserOpenedFieldSelecter(this.currentPresenter.GetTableName());
		}

		//private void toolsFieldSelector_Toggle() { throw new System.NotImplementedException(); }
		//		private void toolsCopyTable_Click(object sender, EventArgs e) { CopyTable(); }
		//		private void toolsRowsetRefresh_Click(object sender, EventArgs e) { RefreshTable(); }

		//		private void toolsSynchTables_Click(object sender, EventArgs e) { throw new System.NotImplementedException(); }
		//		private void toolsAutoRefresh_Click(object sender, EventArgs e) { throw new System.NotImplementedException(); }
		private void ToolsAdhocQueryWindow_Click(object sender, EventArgs e) { this.currentPresenter.UserRequestedAdhocQueryWindow(); }
		private void ToolsSqlTemplate_Click(object sender, EventArgs e) { this.currentPresenter.UserRequestedTemplates(FilterText); }
		private void ToolsCliptoCommaList_Click(object sender, EventArgs e)
		{
			if (Clipboard.ContainsText())
			{
				// get clipdata and replace line endings with ,
				string text = Clipboard.GetData(DataFormats.Text).ToString().Replace("\n", ",").Replace("\r", "");
				// trim off trailing commas
				while (text.Length > 0 && text[text.Length - 1] == ',')
					text = text.Substring(0, text.Length - 1);
				// reset in clip
				Clipboard.SetDataObject(text);
			}
		}
		private void ToolsSetClipTemplate_Click(object sender, EventArgs e)
		{
			ClipboardTemplateForm dialog = new ClipboardTemplateForm(this.clipTemplateString);
			if (dialog.ShowDialog() == System.Windows.Forms.DialogResult.OK)
				this.clipTemplateString = dialog.Template;
		}

		private void ToolsCreatePortableDbViewSharp_Click(object sender, EventArgs e)
		{
			using (DbView.MakePortableAppForm form = new MakePortableAppForm()) { form.ShowDialog(this); }
		}

		private void OptionsToggleFont_Click(object sender, EventArgs e)
		{
			// change all this.
			string dfFontName = "Microsoft Sans Serif";
			string altFontName = "Courier New";
			altFontName = "Arial Unicode MS";

			if (this.currentGrid.Font.FontFamily.GetName(0).CompareTo(altFontName) == 0)
				this.currentGrid.Font = new System.Drawing.Font(dfFontName, 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
			else
				// this.results.Font = new System.Drawing.Font("Courier New", 9.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
				this.currentGrid.Font = new System.Drawing.Font(altFontName, 9.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
		}

		private void OptionsTabbed_Click(object sender, EventArgs e)
		{
			if (this.currentTabMode == TabMode.legacy)
				this.currentTabMode = TabMode.standard;
			else
				this.currentTabMode = TabMode.legacy;
			// persist it
			DbView.Resources.SettingsMgr.Instance.SetValue
						(DbView.Resources.ConfigDoc.CFGELM_NS_UI,
						 DbView.Resources.ConfigDoc.CFGSETTING_TABMODE, this.currentTabMode.ToString());
			OnUpdateTabMode();
		}

		private void OptionsFastFind_Click(object sender, EventArgs e)
		{
			this.FastFindActivated = !this.FastFindActivated;
			// persist it
			DbView.Resources.SettingsMgr.Instance.SetValue
						(DbView.Resources.ConfigDoc.CFGELM_NS_UI,
						 DbView.Resources.ConfigDoc.CFGSETTING_FASTFIND, this.FastFindActivated ? "Y" : "N");
			OnUpdateFastFind();
		}

		private void DoCustomItem(MenuItem m)
		{
			// extended. May not be a just custom item 
			DbViewDriver.CustomMenuItem item = m.Tag as DbViewDriver.CustomMenuItem;
			Command command = Command.Translate(item.CommandTag.ToString());
			if (this.currentTabMode == TabMode.super ||
				this.currentTabMode == TabMode.tableindex && (this.currentPresenter.CurrentCommand.Cmd == Commands.Cmnd.Tables))
			{
				// here to intercept and present the result in a new tab
				if (command.Action == Command.CommandAction.poptable)
				{
					// add a new tab
					// clone the current presenter and attach to tab.
					// make new current.
					NewTabEx(command);
				}
			}
			this.currentPresenter.UserRequestedCommand(command, FilterText);
		}

		private void ChangeFont(MenuItem m)
		{
			this.optionsFontDefault.Checked = false;
			this.optionsFontFixed.Checked = false;
			this.optionsFontUnicode.Checked = false;
			if (m == this.optionsFontDefault)
				this.currentGrid.Font = new System.Drawing.Font("Microsoft Sans Serif", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
			if (m == this.optionsFontFixed)
				this.currentGrid.Font = new System.Drawing.Font("Courier New", 9.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
			if (m == this.optionsFontUnicode)
				this.currentGrid.Font = new System.Drawing.Font("Arial Unicode MS", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
			m.Checked = true;
		}
		private void optionsFont_Click(object sender, EventArgs e)
		{
			ChangeFont((MenuItem)sender);
		}
		private void MenuAggCount_Click(object sender, EventArgs e) { ActionAggregate(Commands.Cmnd.RowCount); }
		private void MenuAggRange_Click(object sender, EventArgs e) { ActionAggregate(Commands.Cmnd.MinMax); }
		private void MenuAggSum_Click(object sender, EventArgs e) { ActionAggregate(Commands.Cmnd.Average); }
		private void MenuAggLongest_Click(object sender, EventArgs e) { ActionAggregate(Commands.Cmnd.LongShort); }
		//
		private void MenuEditEdit_Click(object sender, System.EventArgs e) { this.ChangeEditMode(); }

		private void Go_Click(object sender, System.EventArgs e)
		{
			if (timerConnecting.Enabled) // if the timer is going the UI thinks we're fetching data
				this.currentPresenter.UserRequestedCancelFetch();
			else
				OnSearch(FilterText);
			//m_CurrentPresenter.UserRequestedSearch(FilterText);
		}

		//private void comboView_SelectedIndexChanged(object sender, System.EventArgs e) { throw new System.NotImplementedException(); }
		private void ComboView_SelectionChangeCommitted(object sender, System.EventArgs e)
		{
			if (this.comboView.SelectedIndex >= 0)
				this.currentPresenter.UserSelectedAView(this.comboView.Text);
		}

		private void ToolBar1_ButtonClick(object sender, System.Windows.Forms.ToolBarButtonClickEventArgs e)
		{
			OnToolbarButtonPress(e.Button.ImageIndex);
		}
		
		//private void menuHistory_Popup(object sender, System.EventArgs e)
		//{
		//    this.menuHistory.MenuItems.Clear();
		//    String[] astrMemo = this.m_CurrentPresenter.GetHistoryMemoStrings(true);
		//    // for each item in the history list
		//    // eliminate duplicates
		//    int itemsAdded = 0;
		//    int itemsTested = 0;
		//    foreach (String s in astrMemo)
		//    {
		//        bool found = false;
		//        for (int idx = 0; idx < itemsTested && !found; ++idx)
		//            if (s == astrMemo[idx])
		//                found = true;
		//        ++itemsTested;
		//        if (!found)
		//        {
		//            // add menu item.
		//            MenuItem m = new MenuItem(s, new System.EventHandler(this.menuHistory_Item_Click));
		//            this.menuHistory.MenuItems.Add(m);
		//            ++itemsAdded;
		//        }
		//        if (itemsAdded >= MENU_HISTORY_LIMIT) break; // limits the drop down list
		//    }

		//}
		private void MenuHistory_Popup(object sender, System.EventArgs e)
		{
			this.menuHistory.MenuItems.Clear();
			int currentItem;
			String[] astrMemo = this.currentPresenter.GetHistoryMemoStrings(out currentItem);
			int countDown = MENU_HISTORY_LIMIT; // limits the drop down list
			// for each item in the history list
			foreach (String s in astrMemo)
			{
				// add menu item.
				MenuItem m = new MenuItem(s, new System.EventHandler(this.MenuHistory_Item_Click));
				this.menuHistory.MenuItems.Add(m);
				if (MENU_HISTORY_LIMIT - countDown == currentItem)
					m.Checked = true;
				--countDown;
				if (countDown <= 0) break; // limits the drop down list
			}
		}

		private void MenuHistory_Item_Click(object sender, System.EventArgs e)
		{
			MenuItem m = (MenuItem)sender;
			string strText = m.Text;
			// delegate this to presenter.
			this.currentPresenter.UserRequestedBack(strText);
		}

		//	private void results_ControlAdded(object sender, ControlEventArgs e) { throw new System.NotImplementedException(); }
		private void TimerConnecting_Tick(object sender, EventArgs e)
		{
			ColourChange(1); // 1 means toggle
			timerConnecting.Interval = 200; // speed up once it's started
		}

		private void ColourChange(int option)
		{
			if (option == 1) // toggle
			{
				//	this.m_CancelWorkForm.ShowDialog(this);
				if (this.comboSearchPart.BackColor == System.Drawing.SystemColors.Window)
					this.comboSearchPart.BackColor = Color.OrangeRed;
				else
					this.comboSearchPart.BackColor = System.Drawing.SystemColors.Window;
			}
			else
				this.comboSearchPart.BackColor = System.Drawing.SystemColors.Window;
		}

		//private void statusBar1_PanelClick(object sender, StatusBarPanelClickEventArgs e) { this.m_Presenter.UserRequestedConnect(); }
		private void StatusBar1_PanelClick(object sender, StatusBarPanelClickEventArgs e) { OnPanelClick(e.StatusBarPanel); }

		private void Results_CurrentCellChanged(object sender, System.EventArgs e)
		{
			DataRow dr;
			int rowIndex = this.currentGrid.GetSelectedRow(out dr);
			this.currentPresenter.RowSelected(dr, rowIndex);
			if (this.fieldPanelActive && FieldPanel.Visible)
				// GetTableName() will return "" if the grid is not tables
				this.fieldSelectorPresenter.TableSelected(this.currentPresenter.GetTableName());
		}

		// handle the odd case where certain fields mess up
		private void Results_DataError(object sender, DataGridViewDataErrorEventArgs e)
		{
			// here either when the grid cannot handle the odd data that the DataTable throws at it 
			// (I can't remember the exact case). For this just eat the error
			// Or here if you try to leave the grid with bad input in a cell.
			// In this case lose the edits. Sorry if there were lots. 
			// Update: can't get the logic to work correctly for every case (move cell, select another grid, exit)
			// so I'm leaving it in a hodge podge state
			// move cell: don't trap here trap the exception that seems to be thrown anyway in the mouse click handler
			// exit. Trap this one here (see below) prevent the application closing)
			// select another grid. I'm going to let an error show on this and hope that it is rare
			if (e.Exception != null &&
				(e.Context & DataGridViewDataErrorContexts.Parsing) == DataGridViewDataErrorContexts.Parsing &&
				(e.Context & DataGridViewDataErrorContexts.Commit) == DataGridViewDataErrorContexts.Commit)
			{
				// kill the offending edit.
				// currently not required here.
				//m_CurrentGrid.CancelEdit();
				//m_GridEditErrorState = true;
			}
		}

		private void ComboView_SelectedIndexChanged(object sender, System.EventArgs e)
		{
		}

		private void Results_MouseDown(object sender, System.Windows.Forms.MouseEventArgs e)
		{
			DbViewDataGridView myGrid = (DbViewDataGridView)sender;
			OnMouseDownInGrid(myGrid, e.X, e.Y, (e.Button == MouseButtons.Right));
		}

		private void Results_MouseUp(object sender, System.Windows.Forms.MouseEventArgs e)
		{
			DbViewDataGridView myGrid = (DbViewDataGridView)sender;
			System.Windows.Forms.DataGridView.HitTestInfo hti = myGrid.HitTest(e.X, e.Y);
			if (hti.Type == DataGridViewHitTestType.ColumnHeader)
			{
				if (e.Button == MouseButtons.Left)
				{
					string field = myGrid.MarkSortColumn();
					this.currentPresenter.UserSortedView(field);
				}
			}
		}

		private void Results_MouseDoubleClick(object sender, MouseEventArgs e)
		{
			DbViewDataGridView myGrid = (DbViewDataGridView)sender;
			System.Windows.Forms.DataGridView.HitTestInfo hti;
			hti = myGrid.HitTest(e.X, e.Y);
			switch (hti.Type)
			{
				case DataGridViewHitTestType.RowHeader:
					InvokeDefaultPopup(); // this performs the default right-click menu option
					return;

				case DataGridViewHitTestType.Cell:
					if (!currentGrid.Modifiable)  // when read-only 
						InvokeDefaultPopup(); // this performs the default right-click menu option
					// .. or if in edit mode pass through to activate cell editing
					return;

				case DataGridViewHitTestType.ColumnHeader:
					// Do nothing or popup view configuration 
					return;
				// anywhere else: nothing
			}
		}

		private void MainForm_KeyDown(object sender, KeyEventArgs e)
		{
			//
			/* doesn't do what I want.
			if (e.KeyCode == System.Windows.Forms.Keys.Tab ||
				e.Modifiers == Keys.ControlKey)
			{
				MessageBox.Show("Ping");
			}
			*/
		}

		private void Results_KeyDown(object sender, KeyEventArgs e)
		{
			// required for Enter drills functionality otherwise the
			// cursor skips down a row before drilling.
			// if (e.KeyValue == 13) // return (was this, but Ctrl+M triggered
			if (e.KeyCode == Keys.Oemcomma && e.Control)
			{
				// previous tab
				this.tabs.NavigateTabs(TabCollection.Direction.back);
				e.Handled = true;
			}
			if (e.KeyCode == Keys.OemPeriod && e.Control)
			{
				// Next tab
				this.tabs.NavigateTabs(TabCollection.Direction.forward);
				e.Handled = true;
			}
			if (e.KeyCode == Keys.Enter)
			{
				e.Handled = true;
			}
			else if (e.Control && e.KeyCode == System.Windows.Forms.Keys.F4)
			{
				// close the tab
				if (this.tabs.ActiveTab != null)
					this.tabs.RemoveTab(this.tabs.ActiveTab);
				e.Handled = true;
			}
			//
			// For navigating to marked cells. experimental - discontinued (for now)
			//
			//else if (e.KeyCode == System.Windows.Forms.Keys.F3)
			//{
			//    this.currentGrid.MoveCurrentSelection(e.Shift ? -1 : 1);
			//    e.Handled = true;
			//}
			// add test of shift - doesn't trap shift
			else if (e.KeyCode == System.Windows.Forms.Keys.F10)
			{
				// get current cell
				Point p = new Point(0, 0);
				if (this.currentGrid.CurrentCell != null)
				{
					// line up with the selected row
					Rectangle r = this.currentGrid.GetCellDisplayRectangle(this.currentGrid.CurrentCell.ColumnIndex, this.currentGrid.CurrentCell.RowIndex, true);
					// adjust to be anal.
					int xAdjust = (r.Right - r.Left) / 2;
					if (xAdjust > 10) xAdjust = 10; // max of 10 pixels offset
					p.X = r.Left + xAdjust;
					p.Y = r.Top + (r.Bottom - r.Top) / 2;
					// nb: doesn't show help
					this.currentGrid.ContextMenu.Show(this.currentGrid, p);
				}
				else
				{
					ShowRowIndependentContextMenu(0, 0);
				}
				e.Handled = true;
			}
			// get to the search window in edit mode.
			else if (e.Control && e.KeyCode == Keys.F)
			{
				comboSearchPart.Focus();
				e.Handled = true;
			}
		}

		private void Results_KeyPress(object sender, KeyPressEventArgs e)
		{
			// intercept key presses in grid and forward to the search control
			if (e.KeyChar >= ' ' && e.KeyChar <= 'z')
			{
				string s = e.KeyChar.ToString();
				comboSearchPart.Text = s;
				comboSearchPart.Focus();
				comboSearchPart.Select(s.Length, 0);
				e.Handled = true;
			}
			else if (e.KeyChar == 13) // return
			{
				// drill down
				InvokeDefaultPopup(); // this performs the default right-click menu option 
				e.Handled = true;
			}
			else if (e.KeyChar == 27) // Escape
			{
				//
				// Failed experiment
				//
				//// if something entered in the text box clear it
				//if (comboSearchPart.Text.Length > 0)
				//{
				//    comboSearchPart.Text = "";
				//    comboSearchPart.Focus();
				//}
				//// otherwise back 
				//else
				//    this.currentPresenter.UserRequestedBack();
				this.currentPresenter.UserRequestedBack();
				e.Handled = true;
			}
			else if (e.KeyChar == 8) // back
			{
				// back
				this.currentPresenter.UserRequestedBack();
				e.Handled = true;
			}
		}

		private void OnSearch(String filterText)
		{
			this.currentPresenter.UserRequestedSearch(filterText);
		}

		private void OnMenuFileMru(int number, string filename)
		{
			this.mruMenu.SetFirstFile(number);
			//			this.m_CurrentPresenter.UserRequestedConnect(filename);
			NewTab(filename); // file name is actually the connection tag
		}

		void OnHelpContext()
		{
			helpContext.Checked = !helpContext.Checked;
			// work around a sizing issue when turning off context help
			if (!helpContext.Checked)
			{
				this.currentGrid.ContextMenu = new ContextMenu();
				OnSearch(FilterText);
				//m_CurrentPresenter.UserRequestedSearch(FilterText);
			}
		}

		// Menu select all has 2 different functions
		void OnSelectAll()
		{
			// this branch only when CTRL-A pressed in the Ad-hoc window.
			if (this.textCommands.Focused)
				this.textCommands.SelectAll();
			else
				// otherwise the original use: to select all the rows in the grid.
				this.currentGrid.SelectAllRows();
		}

		// 
		private void OnToolbarButtonPress(int ImageIndex)
		{
			string todoMessage = "";
			switch (ImageIndex)
			{
				case 0: this.currentPresenter.UserRequestedBack(); break;
				case 1: this.currentPresenter.UserRequestedHistory(FilterText); break;
				case 2: this.FileConnect(); break;
				case 3: ClearRestoreSearch(); break;
				case 4: this.currentPresenter.UserRequestedCommand(Commands.Cmnd.Tables, FilterText); break;
				case 5: this.currentPresenter.UserRequestedCommand(Commands.Cmnd.Views, FilterText); break;
				case 6: this.currentPresenter.UserRequestedCommand(Commands.Cmnd.Procedures, FilterText); break;
				case 7: OnEditViewDefinition(); break;
				case 8: SetFilter(); break;
				case 9: ActionAggregate(Commands.Cmnd.Aggregate); break;
				case 10: ActionAggregate(Commands.Cmnd.RowCount); break;
				case 11: this.currentPresenter.UserRequestedSearchSource(""); break;
				case 12: LaunchButtonPressed(); break; //todo:
				case 13: this.toolsFieldSelector_Toggle(); break;
				case 22: this.toolsAdhocQueryWindow_Toggle(); break;
			}
			if (todoMessage.Length > 0)
				MessageBox.Show(todoMessage, "Toolbar Todo");
		}

		// behaviour when status bar panels are clicked
		private void OnPanelClick(StatusBarPanel clickedOnPanel)
		{
			// Filter panel logic
			if (clickedOnPanel == statusBar1.Panels[3])  // 4th panel is filter info.
			{
				if (this.currentPresenter.CurrentCommand.Cmd == Commands.Cmnd.TableData)
				{
					if (this.statusBar1.Panels[3].Text.Length <= "Filter: [   ]".Length) // if empty then pop up the dialog
						SetFilter();
					else
						this.currentPresenter.UserRequestedClearFilter();  // clear filter if it is set.
				}
			}
			// clicked on connection info.
			// in tabbed mode connecting via this will not open a new tab. Slightly unexpected, but edgy case
			// Ideally open the connection or copy the details to clipboard or something else. Not important right now
			else if (clickedOnPanel == statusBar1.Panels[0]) { this.currentPresenter.UserRequestedConnect(); }
			// copy the table name into the clipboard.
			else if (clickedOnPanel == statusBar1.Panels[1]) { Clipboard.SetText(statusBar1.Panels[1].Text); }
		}

		private void ActionAggregate(Commands.Cmnd command)
		{
			GridCellInfo cellInfo = this.currentGrid.GetCurrentCell();
			if (cellInfo != null)
				this.currentPresenter.UserRequestedAggregate(cellInfo, new Command(command));
		}

		private void SetFilter()
		{
			GridCellInfo cellInfo = this.currentGrid.GetCurrentCell();
			this.currentPresenter.UserRequestedFilter(cellInfo);
		}
		/*
				private void CopyTable()
				{
					this.m_Presenter.UserRequestedOperationCopyTable(new CopyTableForm());
				}

				private void RefreshTable()
				{
					this.m_Presenter.UserRequestedOperationRefreshTable(new RefreshTableForm());
				}
		*/

		///////////////////// Helpers //////////////////////////////////////////////////
		string FilterText { get { return this.comboSearchPart.Text.Trim(); } }

		DataTable Data
		{
			get
			{
				// Navigate from the gridview control via it's datasource
				BindingSource bs = (BindingSource)currentGrid.DataSource;
				if (bs == null) return null; // arrives here at odd times on start-up so abandon
				// to the actual table via the datasource of the binding source.
				DataTable dt = (DataTable)bs.DataSource;
				return dt;
			}
		}

		void OnEditViewDefinition()
		{
			// DataTableName is either the view name for table data or string representing
			// the table type
			ComboBox cb = comboView;
			string viewDefKey = (cb.SelectedIndex == -1) ? this.currentGrid.DataTableName
														 : cb.SelectedItem.ToString();
			this.currentPresenter.UserRequestedViewEdit(viewDefKey);
		}

		private void ChangeEditMode()
		{
			// establish current state (Readonly = Edit off otherwise Edit is on
			bool editState = !(this.currentGrid.ReadOnly);
			// Modifiable is the setting in the grid as to whether ior not it can be modified.
			// if not there is no point continuing.
			if (this.currentGrid.Modifiable)
			{
				// if it is possible to modify then grid is either set to readonly Edit mode off
				// or not read only Edit Mode on. We are here to toggle this state.
				// Why the presenter will not now accept and change state is yet to be discovered.
				editState = this.currentPresenter.UserRequestedEdit(!editState);
			}
			// after all this update. Actually this will have been done by the presenter already,
			// so rearrange if you are OCD.
			SetEditMode(editState);
			if (editState == true)
				this.currentGrid.Focus();
		}

		// Set the me
		void SetMenuStates(Commands.Cmnd CurrentCommand)
		{
			bool enable = false;
			// Show
			// only allow fields when tables are shown
			enable = (CurrentCommand == Commands.Cmnd.Tables) ? true : false;
			// only allow data when views or tables are shown
			enable = (CurrentCommand == Commands.Cmnd.Tables) ? true :
					 (CurrentCommand == Commands.Cmnd.Views) ? true :
					 false;
			//
			enable = (CurrentCommand == Commands.Cmnd.TableData) ? true : false;
			// turn off filter unless tabledata.
			this.dataFilter.Enabled = enable;
			this.dataChangeView.Enabled = enable;
			this.dataAggregate.Enabled = enable;
			this.dataSum.Enabled = enable;
			this.dataMinMax.Enabled = enable;
			// tools menu
			enable = (CurrentCommand == Commands.Cmnd.Tables) ? true : false;
			this.toolsFieldSelector.Enabled = enable;
		}

		void SetToolBarStates(Commands.Cmnd CurrentCommand)
		{
			bool enable = false;
			enable = (CurrentCommand == Commands.Cmnd.TableData) ? true : false;
			this.tbAggregate.Enabled = enable;
			this.tbEditView.Enabled = enable;
			this.tbFilter.Enabled = enable;
			this.tbSum.Enabled = enable;

			enable = (CurrentCommand == Commands.Cmnd.Tables) ? true : false;
			this.tbFieldSelecter.Enabled = enable;
		}

		void SetFieldSelectorWindowState(Commands.Cmnd CurrentCommand)
		{
			bool enabled = (CurrentCommand == Commands.Cmnd.Tables);
			if (enabled && this.fieldPanelActive)
				ShowFieldSelector();
			else
			{
				if (this.FieldPanel.Visible)
				{
					this.FieldPanel.Visible = false;
					this.tbFieldSelecter.Pushed = this.FieldPanel.Visible;
					// but leave active flag in whatever styate
				}
			}

		}

		// query the driver for supported commands and enable/disable ui elements
		private void OnChangeDriver(ConnectionInfo ci)
		{
			DbViewDriver.IDataSource datasource = this.currentPresenter.Model.DataSource;
			// fix up the custom menu
			DbViewDriver.CustomMenuItem customMenu = datasource.BuildCustomMenu();
			if (customMenu == null)
				DisableCustomMenu();
			else
				ConfigureCustomMenu(customMenu);
			// set state on standard menus
			SetMenuitemState(Commands.Cmnd.Views, this.menuObjectsViews, datasource);
			SetMenuitemState(Commands.Cmnd.Procedures, this.menuObjectsProcedures, datasource);
			SetMenuitemState(Commands.Cmnd.Triggers, this.menuObjectsTriggers, datasource);
			SetMenuitemState(Commands.Cmnd.Functions, this.menuObjectsFunctions, datasource);
			SetMenuitemState(Commands.Cmnd.ForeignKeys, this.menuObjectsForeignKeys, datasource);
			SetMenuitemState(Commands.Cmnd.Indexes, this.menuObjectsIndexes, datasource);

			SetMenuitemState(Commands.Cmnd.Computed, this.menuObjectsComputed, datasource);
			SetMenuitemState(Commands.Cmnd.Dependencies, this.menuObjectsDependencies, datasource);
			SetMenuitemState(Commands.Cmnd.Permissions, this.menuObjectsPermissions, datasource);

			SetToolbarState(Commands.Cmnd.Views, this.tbView, datasource);
			SetToolbarState(Commands.Cmnd.Procedures, this.tbProc, datasource);

		}
		void SetMenuitemState(Commands.Cmnd command, MenuItem item, DbViewDriver.IDataSource datasource)
		{
			item.Enabled = datasource.SupportsCommand(command);
		}
		void SetToolbarState(Commands.Cmnd command, ToolBarButton tb, DbViewDriver.IDataSource datasource)
		{
			tb.Enabled = datasource.SupportsCommand(command);
		}

		void DisableCustomMenu()
		{
			menuCustom.MenuItems.Clear();
			menuCustom.Visible = false;
		}

		void ConfigureCustomMenu(DbViewDriver.CustomMenuItem customMenu)
		{
			menuCustom.Visible = true;
			menuCustom.Text = customMenu.Name;
			menuCustom.MenuItems.Clear();
			AddCustomMenuItems(menuCustom, customMenu);
		}

		void AddCustomMenuItems(Menu menu, DbViewDriver.CustomMenuItem customMenu)
		{
			foreach (DbViewDriver.CustomMenuItem item in customMenu.Items)
			{
				switch (item.Type)
				{
					case DbViewDriver.CustomMenuItem.ItemType.separator:
						menu.MenuItems.Add(new MenuItem("-"));
						break;
					case DbViewDriver.CustomMenuItem.ItemType.item:
						MenuItem mi = new MenuItem(item.Name, CustomMenuItem_Click);
						mi.Tag = item;
						menu.MenuItems.Add(mi);
						break;
					case DbViewDriver.CustomMenuItem.ItemType.menu:
						// unsupported.
						break;
				}
			}
		}

		//////////////////////////////////////////////////////////////////////////////
		// New Presenter New tab;
		// new tab logic deconstructed.
		// if running then pre-check there are no edits and abort 
		// close the field selector
		void NewTabOnStart()
		{
			this.currentPresenter = new MainPresenter();
			ToolStripItem btn = ConfigureNewTab(false); // false means add to end
			// special bootstrap. Find the last connection used in the previous session
			// and seed the presenter with it. 
			InitialiseConnectionfromMRU(this.mruMenu);

			// attempt connect to a new database. If false then user cancelled.
			if (this.currentPresenter.MakeInitialConnection(this.initialConnectionTag) == false)
				this.tabs.RemoveTab(btn);
			else
				this.tabs.SetActive(btn);
		}

		void NewTab(String connectionTag)
		{
			this.currentPresenter = new MainPresenter();
			ToolStripItem btn = ConfigureNewTab(false); // false means add to end
			//			SetTabTitle(String.Format("[ {0} ]", connectionTag));
			SetTabTitle(String.Format("New", connectionTag)); // prefer above, but this is easier to ID

			// attempt connect to a new database. If false then user cancelled.
			if (this.currentPresenter.MakeInitialConnection(connectionTag) == false)
				this.tabs.RemoveTab(btn);
			else
				this.tabs.SetActive(btn);
		}

		private ToolStripItem ConfigureNewTab(bool insert)
		{
			// basic wire-up of objects
			this.currentPresenter.attachView(this);
			// share the model with the field display present to give the latter access to the database
			this.fieldSelectorPresenter.AttachModel(this.currentPresenter.Model);

			// to display in the background while 
			if (insert)
				this.tabs.InsertTabAfterSelected("NewTab", "New", this.currentPresenter, false);
			else
				this.tabs.AddTab("NewTab", "New", this.currentPresenter, false);
			// get a reference to the button just created
			ToolStripItem btn = this.tabs.FindUserData(this.currentPresenter);
			// Experimental
			this.tabs.MoveToStart(btn);
			return btn;
		}

		private PresenterGridState SaveGridState()
		{
			int row = this.currentGrid.CurrentCellAddress.Y;
			DataTable dt = this.currentGrid.GridDataTable;
			PresenterGridState savedState = new PresenterGridState(dt, row);
			savedState.IsFieldSelectorVisible = this.holdFieldPanelActive;
			savedState.IsAdhocDisplayed = this.IsAdhocShown;
			savedState.AdhocText = (savedState.IsAdhocDisplayed) ? this.textCommands.Text : "";
			// Navigate from the gridview control via it's datasource
			savedState.Results = GetResults();
			return savedState;
		}

		// This does much the same as above except that it clones the current presenter and
		// executes a specific command
		// V1 will not clone, but I've not much hope of this working
		private void NewTabEx(Command command)
		{
			if (!PreNewTabCheck()) return;

			// nb: ought to commit edits first
			// switch current to new presenter an initial presenter to control the application
			MainPresenter prevPresenter = this.currentPresenter; // hold this for restoration later
			this.currentPresenter = prevPresenter.Clone();
			ToolStripItem btn = ConfigureNewTab(true); // true means insert after active
			// connect to a new database
			{
				this.tabs.SetActive(btn);
			}
		}

		private void LaunchButtonPressed()
		{
			if (this.currentTabMode == TabMode.legacy)
			{
				this.currentPresenter.UserRequestedNewInstance();
			}
			else
			{
				if (this.currentTabMode == TabMode.standard)
				{
					// create a copy
					NewTab(false);
				}
				else
				{
					if (tbLaunch.Pushed == true)
						this.currentTabMode = TabMode.super;
					else
						this.currentTabMode = TabMode.tableindex;
				}
				// just in case
				OnUpdateTabMode();
			}
		}

		private void OnUpdateTabMode()
		{
			// set launch button state.
			// toggle if tabbed ; normal push if 
			this.tbLaunch.Style = (this.currentTabMode == TabMode.legacy) ? ToolBarButtonStyle.PushButton : ToolBarButtonStyle.ToggleButton;
			// if a toggle button set the state
			if (this.tbLaunch.Style == ToolBarButtonStyle.ToggleButton)
				this.tbLaunch.Pushed = (this.currentTabMode == TabMode.super) ? true : false;
			// set the toolstrip style
			TabCollection.Visibility vs = (this.currentTabMode == TabMode.legacy) ? TabCollection.Visibility.never :
											(this.currentTabMode == TabMode.standard) ? TabCollection.Visibility.many :
											(this.currentTabMode == TabMode.super) ? TabCollection.Visibility.always :
											TabCollection.Visibility.one; // this never reached
			this.tabs.SetVisibilityMode(vs);
			// set the menu check
			optionsTabbed.Checked = (this.currentTabMode == TabMode.legacy) ? false : true;
		}
		
		private void OnUpdateFastFind()
		{
			// set the menu check
			optionsFastFind.Checked = this.FastFindActivated;
		}

		/*
			extend connection logic. 
			Previously we just informed the presenter. Now we need to check the 
			tab options and open a new tab if necessary
		*/
		void FileConnect()
		{
			if (this.currentTabMode == TabMode.legacy)
				// connect and display in the current tab
				this.currentPresenter.UserRequestedConnect();
			else // other tab modes will open a new tab on a successful connection
				NewTab(true);
		}

		private void NewTab(bool newConnection)
		{
			if (!PreNewTabCheck()) return;
			// nb: ought to commit edits first
			// switch current to new presenter an initial presenter to control the application
			MainPresenter prevPresenter = this.currentPresenter; // hold this for restoration later
			this.currentPresenter = new MainPresenter();
			ToolStripItem btn = ConfigureNewTab(false); // false to insert at end

			// connect to a new database
			if (newConnection)
			{
				// trying to get previous connection to be set in the connection dialog
				this.currentPresenter.Model.SetDriver(prevPresenter.Model.DataSource);
				this.currentPresenter.Model.SetConnection(prevPresenter.Model.GetCurrentConnection());
				// try it
				bool connected = this.currentPresenter.UserRequestedConnect();
				// failed or user aborted.
				if (!connected)
				{
					// try restore, but can't if this is first.
					// possible bootstrap case. We always need a presenter
					if (prevPresenter != null)
					{
						ToolStripItem btnPrev = this.tabs.FindUserData(prevPresenter);
						if (btn != null)
							this.tabs.RemoveTab(btn, btnPrev);
						this.currentPresenter = prevPresenter;
						// resynch field selector
						this.fieldSelectorPresenter.AttachModel(this.currentPresenter.Model);
						// enough? the grid should not have changed
					}
				}
				else
				{
					// activate
					this.tabs.SetActive(btn);
				}
			}
			else
			{
				string cloneConnectionName = prevPresenter.Model.CurrentConnection;
				this.currentPresenter.MakeInitialConnection(cloneConnectionName);
				this.tabs.SetActive(btn);
			}
		}

		private bool PreNewTabCheck()
		{
			// presenter refuses to be abandoned
			if (this.currentPresenter.UserRequestedNewTab() == false) return false;

			// Close field selector if necessary
			// hold the active value first as it is required later on for state
			this.holdFieldPanelActive = this.fieldPanelActive;
			// below always resets this.m_FieldPanelActive
			CloseFieldSelectorWindow();
			return true;
		}

		DbViewDataGridView MakeNewGridView()
		{
			DbView.DbViewDataGridView dgv = new DbView.DbViewDataGridView();
			// 
			// results
			// 
			dgv.AllowUserToAddRows = false;
			dgv.AutoSizeColumnsMode = System.Windows.Forms.DataGridViewAutoSizeColumnsMode.AllCells;
			dgv.BackgroundColor = System.Drawing.SystemColors.Desktop;
			dgv.BorderStyle = System.Windows.Forms.BorderStyle.None;
			//dgv.ContextMenu = this.thePopup;
			dgv.ContextMenu = new ContextMenu();
			dgv.Dock = System.Windows.Forms.DockStyle.Fill;
			dgv.Font = new System.Drawing.Font("Microsoft Sans Serif", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
			dgv.GridColor = System.Drawing.SystemColors.ControlLight;
			dgv.Location = new System.Drawing.Point(0, 50);
			dgv.Name = "results";
			dgv.RowHeadersWidth = 32;
			dgv.RowTemplate.Height = 18;
			dgv.RowTemplate.Resizable = System.Windows.Forms.DataGridViewTriState.True;
			dgv.Size = new System.Drawing.Size(657, 419);
			dgv.TabIndex = 0;
			// Todo: detach these handlers to improve gc.
			// Actually no not yet as there is only one grid view. It's the tables that are switched i and out.
			AttachGridEventHandlers(dgv);

			return dgv;
		}

		private void AttachGridEventHandlers(DbView.DbViewDataGridView dgv)
		{
			dgv.CurrentCellChanged += new System.EventHandler(this.Results_CurrentCellChanged);
			dgv.DataError += new System.Windows.Forms.DataGridViewDataErrorEventHandler(this.Results_DataError);
			dgv.KeyDown += new System.Windows.Forms.KeyEventHandler(this.Results_KeyDown);
			dgv.KeyPress += new System.Windows.Forms.KeyPressEventHandler(this.Results_KeyPress);
			dgv.MouseDoubleClick += new System.Windows.Forms.MouseEventHandler(this.Results_MouseDoubleClick);
			dgv.MouseDown += new System.Windows.Forms.MouseEventHandler(this.Results_MouseDown);
			dgv.MouseUp += new System.Windows.Forms.MouseEventHandler(this.Results_MouseUp);
		}

		private void DetachGridEventHandlers(DbView.DbViewDataGridView dgv)
		{
			dgv.CurrentCellChanged -= new System.EventHandler(this.Results_CurrentCellChanged);
			dgv.DataError -= new System.Windows.Forms.DataGridViewDataErrorEventHandler(this.Results_DataError);
			dgv.KeyDown -= new System.Windows.Forms.KeyEventHandler(this.Results_KeyDown);
			dgv.KeyPress -= new System.Windows.Forms.KeyPressEventHandler(this.Results_KeyPress);
			dgv.MouseDoubleClick -= new System.Windows.Forms.MouseEventHandler(this.Results_MouseDoubleClick);
			dgv.MouseDown -= new System.Windows.Forms.MouseEventHandler(this.Results_MouseDown);
			dgv.MouseUp -= new System.Windows.Forms.MouseEventHandler(this.Results_MouseUp);
		}
		/////////////////////////////////////////////////////////////////////////
		// ITabObserver interface implementation
		public void TabAdded(String tag, object userData)
		{

		}
		public void TabActivated(String tag, object userData, object prevUserData)
		{
			// Thanks for passing in the user data from the tab move from
			MainPresenter prevPresenter = prevUserData as MainPresenter;
			PreNewTabCheck(); // we're plowing on despite what this returns.
			// what to do?
			//// EXPERIMENTAL (good so far) remove requirement to fetch from the database when switching view
			if (prevPresenter != null) // null test catches edge cases eg. tab control being careless with it's callbacks
				SaveGridState(prevPresenter); // ideal time to preserve the current grid view state (row index and data table)
			// now close down adhoc if it is present
			textCommands.Text = "";
			HideAdhoc();
			SetResults(null);
			// this.dgResults.DataSource = null; // don't like this need to preserve it
			//// end EXPERIMENTAL remove requirement to fetch from the database when switching view

			// Rewire the form and grid to the presenter associated with the new active tab
			this.currentPresenter = userData as MainPresenter;
			// Hack. Main form should not need to know this
			//m_CurrentPresenter.Model.ConnectionList.CurrentDriverName = this.m_CurrentPresenter.Model.DataSource.DriverName; 
			try
			{
				// If a switch to another connection for another driver fails then this attempts to revert
				// to the previous connection but cannot as the driver is incorrect.  
				// this.m_CurrentPresenter.Model.SetCurrentConnection(this.m_CurrentPresenter.Model.GetCurrentConnection());
				// this is more thorough
				this.currentPresenter.Model.SetConnection(this.currentPresenter.Model.GetCurrentConnection());
			}
			catch (DbViewApplicationException exc)
			{
				string message = string.Format("An error ocurred activating the tab\nIt will be closed. The error was\n{0}", exc.Message);
				InformUser(message, "Error activating " + this.tabs.ActiveTab.Text, true);
				this.tabs.RemoveTab(this.tabs.ActiveTab);
				return;
				//if (prevPresenter == null)
				//{
				//    return; 
				//}
				//// catch case where connection has been removed.
				//m_CurrentPresenter = prevPresenter.Clone();
				//// set the title
			}

			this.currentPresenter.attachView(this);
			//// EXPERIMENTAL (good so far) remove requirement to fetch from the database when switching view
			if (this.gridStateMap.ContainsKey(this.currentPresenter))
			{
				// we've been on this tab before so try to put it back as close as possible
				// without asking the presenter to repeat the sql
				PresenterGridState savedState = this.gridStateMap[currentPresenter];
				if (savedState != null)
				{
					// change true to some bool to restore edit state
					ResetGrid(savedState.Table, !currentPresenter.IsDataEditable(), savedState.RowIndex);
					// Field selector state
					this.fieldPanelActive = savedState.IsFieldSelectorVisible;
					// restore adhoc window
					if (savedState.IsAdhocDisplayed)
					{
						ShowAdhoc(false);
						textCommands.Text = savedState.AdhocText;
						this.currentPresenter.AdHocCommandTextChanged(savedState.AdhocText);
						SetResults(savedState.Results);
						//BindingSource bSource = new BindingSource();
						//// set the BindingSource DataSource
						//bSource.DataSource = savedState.Results;
						//// set the DataGridView DataSource
						//this.dgResults.DataSource = bSource;
					}
					else
						HideAdhoc();
				}
			}
			//// EXPERIMENTAL remove requirement to fetch from the database when switching view
			// share the model with the field display present to give the latter access to the database
			this.fieldSelectorPresenter.AttachModel(this.currentPresenter.Model);

			// Experimental. Below does not work well.
			// this.m_Tabs.MoveToStart(this.m_Tabs.FindUserData(userData));

			// force presenter to restore application state
			// Set true if we need to rebuild the report. false otherwise
			// Hoping not to rebuild the report as this is faster.
			// Informing the presenter as it will need to restore the context menu
			this.currentPresenter.UserReactivatedView(false);
		}

		private void SaveGridState(MainPresenter prevPresenter)
		{
			if (!gridStateMap.ContainsKey(prevPresenter))
				this.gridStateMap.Add(prevPresenter, SaveGridState());
			else
				this.gridStateMap[prevPresenter] = SaveGridState();
		}

		public bool IsTabOkToLeave(String tag, object userData)
		{
			// Workaround a tricky issue in getting a current cell update to trigger the table dirty state
			CheckUpdateCurrentCellEdit();
			// Check for unsaved edits.a
			MainPresenter tagPresenter = userData as MainPresenter;
			if (tagPresenter.UserRequestedNewTab() == false) return false;
			
			// check whether running a query
			if (timerConnecting.Enabled) // if the timer is going the UI thinks we're fetching data
			{
				int answer = this.AskUser("The system is busy fetching data. Do you wish to cancel this operation and continue to change tab?","Cancel current data fetch", false);
				// no don't cancel the query
				if (answer == 0)
					return false;
				// cancel and proceed (or the query may have finished now)
				this.currentPresenter.UserRequestedCancelFetch();
			}
			return true;
		}

		// This gets a single cell being updated to trigger the datatable row
		// change state. It uses the hacky means of switch focus away from and back
		// the grid view (discivered by experimentation).
		// It means that a single cell edit will trigger the save edits message when
		// switching away from the window
		// Update 2012. Hopefully EndEdit() is the official way. 
		private bool CheckUpdateCurrentCellEdit()
		{
			// Only bother with this when in edit mode.
			// Use the edit menu check state as a proxy test for being in edit mode.
			if (menuEditEdit.Checked)
			{
				// this can cause an error. Can't think of a better way to track it than a flag member
				this.gridEditErrorState = false;
				this.currentGrid.EndEdit();
				//// set focus to a random control in the form (but one without special focus logic)
				//// when the gridview loses focus it updates the datasource (ie. our table)
				//this.comboSearchPart.Focus();
				//// switch back to the grid to disguise what is going on.
				//this.m_CurrentGrid.Focus();
			}
			if (this.gridEditErrorState == true)
			{
				this.gridEditErrorState = false; // reset the flag directly
				return false;
			}
			return true;
		}

		public bool IsTabOkToRemove(String tag, object userData)
		{
			// apply the unsaved edits check.
			if (!IsTabOkToLeave(tag, userData)) return false;

			// allow if booting or closing, but not if running as it may be by accident
			if (this.applicationState != Appstate.running) return true;
			// so here only if running
			if (this.TabHost.Items.Count == 1)
			{
				string message = @"
You are not permitted to close the final tab until you create another.
If you wish to close the application then please use one of the 
standard methods. If you wish to disconnect from the present database then
please select File, Connect from the menu or press the Connection icon 
on the toolbar.
";
				InformUser(message, "Close tab not permitted", true);
				return false;
			}
			return true; // false if eg dirty data
		}
		public void TabRemoved(String tag, object userData, bool IsActive)
		{
			// indeed if not tabs are left close the app (like Chrome)
			// better would be an API call
			if (this.TabHost.Items.Count == 0)
			{
				//m_CurrentPresenter.UserRequestedExit();
				return;
			}
			// if it's active unbind it.
			if (IsActive)
				this.currentGrid.UnBindDataTable();
		}
		public void TabMoved(String tag, object userData, int oldIndex, int newIndex)
		{

		}
		// ITabObserver interface implementation
		/////////////////////////////////////////////////////////////////////////

		// End Tab Control operations
		//////////////////////////////////////////////////////////////////////////////

		// Lots of fun when you click in a grid.
		private void OnMouseDownInGrid(DbViewDataGridView myGrid, int gridX, int gridY, bool isRightClick)
		{
			// find out where in the grid the click happened
			DataGridView.HitTestInfo hti = myGrid.HitTest(gridX, gridY);
			// register the new row in case actions outside of this method require it.
			UpdateRowPos(hti.RowIndex);
			// Click on the left margin of the row
			if (hti.Type == DataGridViewHitTestType.RowHeader)
			{
				// let the presenter know what data is highlighted. This is to provide it with context.
				DataRow dr;
				myGrid.GetSelectedRow(out dr);
				this.currentPresenter.RowSelected(dr, hti.RowIndex);
				// Want a context menu
				if (isRightClick)
				{
					// often the first column of data is sufficient context. This might be old now.
					// wait. This is for potential use in the context help
					string objectName = "";
					// Set the current cell to cell1, row 1.
					if (myGrid.CurrentRow != null) // null if in the edit row
					{
						if (myGrid.CurrentRow.Index != hti.RowIndex)
							myGrid.CurrentCell = myGrid.Rows[hti.RowIndex].Cells[0];
						objectName = myGrid.Rows[hti.RowIndex].Cells[0].Value.ToString();
					}
					// add context help if the option is enabled.
					PreDisplayContextMenu(objectName, helpContext.Checked); // set to option when ready
					// all ready to show now
					this.currentGrid.ContextMenu.Show(this.currentGrid, new Point(gridX, gridY));
				}
			}

			// This is the top left grey square
			// this for showing SQL which produces no rows.
			if (hti.Type == DataGridViewHitTestType.TopLeftHeader)
			{
				if (isRightClick)
				{
					ShowRowIndependentContextMenu(gridX, gridY);
				}
			}
			// click in a cell 
			if (hti.Type == DataGridViewHitTestType.Cell)
			{
				// decide whether to reset the selection on right-click
				bool resetCurrent = true;  // assume yes
				// if the hit is in the selection then leave the selection intact
				foreach (DataGridViewCell cell in myGrid.SelectedCells)
				{
					if (cell.ColumnIndex == hti.ColumnIndex && cell.RowIndex == hti.RowIndex)
					{
						resetCurrent = false;
						break;
					}
				}

				if (resetCurrent)
				{
					try
					{
						myGrid.CurrentCell = myGrid.Rows[hti.RowIndex].Cells[hti.ColumnIndex];
					}
					// this exception it thrown when you use the cursor to move away from a cell with invalid content
					// eg. letters in a numeric cell
					catch (InvalidOperationException)
					{
						// check it's an edit case
						if (myGrid.IsCurrentCellInEditMode)
						{
							if (isRightClick)
							{
								this.InformUser("Your current edit is invalid. Please fix or discard it.", "Entry error detected", false);
								return; // asking doesn't work very well in right click mode. Lets see what this does.
							}

							// Offer the choice of continuing if it was an accident or returning and fixing the code.
							int answer = this.AskUser("Your current edit is invalid. Do you want to discard it and continue?", "Entry error detected", false);
							if (answer == 1) // yes
								myGrid.CancelEdit();
							else
								return; // 
						}
						else
							throw; // rethrow. I don't know what would cause this.
					}
				}

				if (isRightClick)
				{
					bool wantMenu = this.IsSingleColumnSelected(myGrid) && this.IsLookupColumn(myGrid, hti.ColumnIndex);
					if (wantMenu)
					{
						ShowLookupContextMenu(gridX, gridY, hti.ColumnIndex);
					}
					else
						// Normal branch: copy to clip and flash
						CopyGridCellData(myGrid, hti.ColumnIndex);
				}
				else // left click
				{
					if (!currentGrid.Modifiable)
					{
						myGrid.BeginEdit(true);
					}
				}
			}
			if (hti.Type == DataGridViewHitTestType.ColumnHeader)
			{
				// right click in the column header copies the text (eg field name) into clip.
				if (isRightClick)
				{
					DataGridViewColumn dc = myGrid.Columns[hti.ColumnIndex];
					// just copy all marked to clipboard for me
					Clipboard.SetDataObject(dc.Name);

					// below just provides a visual confirmation that the selection is made
					// get the selction background as the cell was selected above
					Color c = dc.HeaderCell.Style.BackColor;

					// temporarily set the selection colour to red and update screen
					dc.HeaderCell.Style.BackColor = Color.Red;
					myGrid.Refresh();
					// short delay so colour change is apparent to user
					System.Threading.Thread.Sleep(100);
					// restore
					dc.HeaderCell.Style.BackColor = c;
					myGrid.Refresh();
				}
			}
		}

		private void ShowLookupContextMenu(int gridX, int gridY, int columnIndex)
		{
			// this needs cutting down
			ContextMenu lookupContextMenu = new ContextMenu();
			MenuItem mi = new MenuItem("Lookup", RightClickLookup_Click);
			mi.Tag = columnIndex;
			lookupContextMenu.MenuItems.Add(mi);

			mi = new MenuItem("Copy", RightClickCopy_Click);
			mi.Tag = columnIndex;
			lookupContextMenu.MenuItems.Add(mi);

			lookupContextMenu.Show(this.currentGrid, new Point(gridX, gridY));
		}
		
		private void RightClickCopy_Click(object sender, System.EventArgs e)
		{
			MenuItem mi = sender as MenuItem;
			int columnIndex = System.Convert.ToInt32(mi.Tag);
			CopyGridCellData(this.currentGrid, columnIndex);
		}

		private void RightClickLookup_Click(object sender, System.EventArgs e)
		{
			// Get column index selected from the tag data attached to the menu item
			MenuItem mi = sender as MenuItem;
			int columnIndex = System.Convert.ToInt32(mi.Tag);
			// Get the data. Should really only be here when a single column is selected
			// TODO: make this so.
			CopyCellDataToClipBoard(this.currentGrid.GetClipboardContent(), false);
			string clipData = Clipboard.GetText();
			// Multiple cells need converting from a cr list to a comma-list
			// we're being lazy with the type so to be safe enclose the data in quotes
			clipData = "'"+clipData.Replace("\r\n","\n").Replace("\n", "','")+"'";

			LookupModel lu = this.MakeLookupModel(this.currentGrid, columnIndex);
			// TODO: replace sql with data and build in the dialog.
			LookupResultForm dlg = new LookupResultForm(this.currentPresenter.Model.DataSource, this.currentPresenter.Model.CurrentConnection, lu, clipData);
			dlg.Show();
		}
		
		private LookupModel MakeLookupModel(DbViewDataGridView grid, int columnIndex)
		{
			DataTable dt = grid.GridDataTable;
			DataTableExtraInfoManager mgr = new DataTableExtraInfoManager(dt);
			DbView.Resources.LookupInfo.Key key = new DbView.Resources.LookupInfo.Key(this.currentPresenter.Model.CurrentDriver, this.currentPresenter.Model.CurrentConnection,
																			this.currentPresenter.GetTableName(), dt.Columns[columnIndex].ColumnName);
			DbView.Resources.LookupInfo.Data data = DbView.Resources.LookupInfo.Instance.FindData(key);
			LookupModel model = LookupModel.DataTransfer(key, data);
			return model;
		}
		
		private bool IsLookupColumn(DbViewDataGridView grid, int columnIndex)
		{
		    DataTable dt = grid.GridDataTable;
		    DataTableExtraInfoManager mgr = new DataTableExtraInfoManager(dt);
		    string field = mgr.GetLookupKeyField(dt.Columns[columnIndex]);
		    return string.IsNullOrEmpty(field) ? false : true;
		}

		bool IsSingleColumnSelected(DbViewDataGridView grid)
		{
			DataObject clipData = grid.GetClipboardContent();
			if (!clipData.ContainsText())
				return false;
			string data = clipData.GetText();
			return !data.Contains("\t");
		}

		private void CopyGridCellData(DbViewDataGridView myGrid, int columnIndex)
		{
			DataRow dr;
			Helper.GetSelectedRow(myGrid, out dr);
			// account for null datarow return (eg click in the new row)
			//string data = (dr == null) ? "" : dr[hti.ColumnIndex].ToString().Trim();
			string data = (dr == null) ? "" : dr[columnIndex].ToString().Trim();
			// copy all marked to clipboard, applying the template if any is set
			CopyCellDataToClipBoard(myGrid.GetClipboardContent(), true);

			// below just provides a visual confirmation that the selection is made
			// get the selction background as the cell was selected above
			Color c = myGrid.DefaultCellStyle.SelectionBackColor;
			// temporarily set the selection colour to red and update screen
			// additional queue: set colour depending on whether a template is active or not.
			myGrid.DefaultCellStyle.SelectionBackColor = (this.clipTemplateString.Length > 0) ? Color.Plum : Color.Red;
			myGrid.Refresh();
			// short delay so colour change is apparent to user
			System.Threading.Thread.Sleep(100);
			// restore
			myGrid.DefaultCellStyle.SelectionBackColor = c;
			myGrid.Refresh();

			// new: (2011). If large text popup sourceform box
			if (data.Length > 192 || data.Contains("\n")) // MAGIC NUMBER!!!!
			{
				ShowSource("", Helper.FormatLongCellDataForDisplay(data), "");
			}
		}

		private void ShowRowIndependentContextMenu(int gridX, int gridY)
		{
			// this needs cutting down
			ContextMenu rowIndependentContextMenu = new ContextMenu();
			foreach (MenuItem mi in this.currentGrid.ContextMenu.MenuItems)
			{
				DbViewDriver.CustomMenuItem item = mi.Tag as DbViewDriver.CustomMenuItem;
				if (item != null)
					if (item.IsRowIndependent)
					{
						MenuItem newMi = mi.CloneMenu();
						newMi.Tag = mi.Tag;
						rowIndependentContextMenu.MenuItems.Add(newMi);
					}
			}
			if (rowIndependentContextMenu.MenuItems.Count > 0)
				rowIndependentContextMenu.Show(this.currentGrid, new Point(gridX, gridY));
		}

		void PreDisplayContextMenu(String objectName, bool wantMenuHelp)
		{
			foreach (MenuItem mi in this.currentGrid.ContextMenu.MenuItems)
			{
				DbViewDriver.CustomMenuItem item = mi.Tag as DbViewDriver.CustomMenuItem;
				if (item != null)
				{
					mi.Text = item.Name.Replace("[ ]", objectName);
					if (wantMenuHelp && item.Help.Length > 0)
						mi.Text += " : " + item.Help;
				}
				foreach (MenuItem mi2 in mi.MenuItems)
				{
					item = mi2.Tag as DbViewDriver.CustomMenuItem;
					if (item != null)
					{
						mi2.Text = item.Name.Replace("[ ]", objectName);
					}
				}
			}
		}

		private bool CopyCellDataToClipBoard(DataObject dataObject, bool respectFormatting)
		{
			if (dataObject == null) return false; // can't do anything more in this situation
			// not safe to do anything unless clip data is text.
			// do nothing if there is nothing in the template string
			if (this.clipTemplateString.Length == 0 || respectFormatting == false || !dataObject.ContainsText())
			{
				Clipboard.SetDataObject(dataObject);
				return false; // ie. template na or not applied
			}
			string cellData = dataObject.GetText();
			// apparently you may need to clear before you reset
			Clipboard.Clear();
			// needs more work to burn out the data
			// this suits me for now
			try
			{
				if (cellData.IndexOf((char)13) == -1)
				{
					string row = FormatCellDataRow(cellData);
					Clipboard.SetText(row);
				}
				else
				{
					// multiple rows
					String[] a = cellData.Replace("\r", "").Split('\n');
					List<String> formatted = new List<string>();
					foreach (String line in a)
					{
						formatted.Add(FormatCellDataRow(line));
					}
					cellData = String.Join("\n", formatted.ToArray());
					Clipboard.SetText(cellData);
				}
			}
			catch (DbViewApplicationException exc)
			{
				InformUser(exc.Message, "Error Copying to Clipboard", true);
			}

			return true;
		}

		string FormatCellDataRow(String CellDataRow)
		{
			// split the cells by 
			String[] cells = CellDataRow.Split('\t');
			// prevent mismatch of {} and params
			try
			{
				return string.Format(this.clipTemplateString, cells);
			}
			catch (FormatException)
			{
				string message = @"
The clipboard template format requires more columns than
you have selected from the grid. Either:
a. Select more columns or
b. Change or clear the clipboard template
";
				throw new DbViewApplicationException(message);
				// MessageBox.Show(message, "Copy to clipboard error", MessageBoxButtons.OK, MessageBoxIcon.Error);
				// return string.Format("{0} (!template error)", CellDataRow);
			}
		}

		private void UpdateRowPos(int row)
		{
			DataTable dt = this.Data;
			if (dt == null) return; // abandon if the data is not ready.

			int totalRows = dt.Rows.Count;
			int rowPanel = 2;
			string panelText = string.Format("{0}/{1}", row + 1, totalRows);
			// prevent edits unless user 
			if (row < 0 || row >= totalRows) panelText = string.Format("{0}", totalRows);
			statusBar1.Panels[rowPanel].Text = panelText;

		}

		private void InvokeDefaultPopup()
		{
			// assume forwarded to logic handles this  this.begin_action();           
			// find default
			int iClickItem = -1;  // default do nothing
			// hunt for default item in context
			for (int idx = 0; idx < this.currentGrid.ContextMenu.MenuItems.Count; ++idx)
				if (this.currentGrid.ContextMenu.MenuItems[idx].DefaultItem == true)
					iClickItem = idx;
			// no default item (sloppy or no items)
			if (iClickItem == -1)
				if (this.currentGrid.ContextMenu.MenuItems.Count > 0)
					iClickItem = 0; // assume 1st item if there is one
			// if a valid index then invoke the item.
			if (iClickItem >= 0)
				this.currentGrid.ContextMenu.MenuItems[iClickItem].PerformClick();
			// this.end_action();            
		}

		private void ClearRestoreSearch()
		{
			if (this.comboSearchPart.Text.Length == 0 &&  // search text is empty
				this.comboSearchPart.Items.Count > 0      // and something in history
				)
			{
				this.comboSearchPart.Text = this.comboSearchPart.Items[0].ToString(); // reset to most recent
			}
			else // Text is not empty or there is no previous search to use
				this.comboSearchPart.Text = ""; // reset or keep blank
			// 
			OnSearch(FilterText);
			//m_CurrentPresenter.UserRequestedSearch(FilterText);
		}

		private void SetWindowTitle(String connection)
		{
			int iPos = this.Text.IndexOf(':');
			string old = (iPos == -1) ? this.Text : this.Text.Substring(0, iPos);
			this.Text = string.Format("{0}: [ {1} ]", old, connection);
		}

		private void SetTabTitle(String caption)
		{
			ToolStripItem tab = this.tabs.FindUserData(this.currentPresenter);
			if (tab != null)
			{
				// this strips out the extension text just to neaten the 
				string adjustedCaption = caption.Replace(DbView.Commands.Cmnd.Extension.ToString() + ":", "");
				tab.Text = adjustedCaption;
			}
		}
		///////////////////////////////////////////////////////////
		// field selector stuff
		///////////////////////////////////////////////////////////
		private void toolsFieldSelector_Toggle()
		{
			if (this.FieldPanel.Visible == false) // on
				ToolsFieldSelector_Click(null, null);
			else
			{
				CloseFieldSelectorWindow();
			}
		}
		///////////////////////////////////////////////////////////
		// Interface implementation
		public void DisplaySelectedFields(List<String> fieldNames, bool isPartialList)
		{
			if (isPartialList)
				listFields.ForeColor = System.Drawing.Color.Red;
			else
				listFields.ForeColor = System.Drawing.SystemColors.WindowText;
			listFields.Items.Clear();
			foreach (String fieldName in fieldNames)
			{
				listFields.Items.Add(DecorateWithPrefix(fieldName));
			}
		}

		private object DecorateWithPrefix(string fieldName)
		{
			string prefix = this.fieldSelectorPresenter.Prefix;
			if (prefix.Length == 0) return fieldName;
			return string.Format("{0}.{1}", prefix, fieldName);
		}

		public void DisplaySelectedTableName(String tableName)
		{
			this.textTable.Text = tableName;
		}

		///////////////////////////////////////////////////////////
		// Events
		private void btnClose_Click(object sender, EventArgs e)
		{
			CloseFieldSelectorWindow();
		}

		private void CloseFieldSelectorWindow()
		{
			this.FieldPanel.Visible = false;
			this.fieldPanelActive = false;
			this.tbFieldSelecter.Pushed = this.FieldPanel.Visible;
		}

		private void listFields_KeyPress(object sender, System.Windows.Forms.KeyPressEventArgs e)
		{
			char c = e.KeyChar;
			bool wanted = this.fieldSelectorPresenter.KeyPress(c);
		}

		private void btnAll_Click(object sender, EventArgs e)
		{
			this.fieldSelectorPresenter.ShowAll();
		}

		private void listFields_SelectedIndexChanged(object sender, EventArgs e)
		{
			// below causes problems in keyboard mode.
			// but is very useful
			copySelected(FieldSelectorPresenter.CopyMode.block);
		}

		private void listFields_MouseDown(object sender, MouseEventArgs e)
		{
			if (e.Button == MouseButtons.Right)
			{
				int idx = listFields.IndexFromPoint(new Point(e.X, e.Y));
				if (idx == ListBox.NoMatches) return;
				listFields.SelectedIndex = idx;
				Clipboard.SetText(listFields.Items[idx].ToString());
			}
		}
		private void btnCopy_Click(object sender, EventArgs e)
		{
			// copy the selected in block mode
			copySelected(FieldSelectorPresenter.CopyMode.block);
		}

		private void btnCopyAll_Click(object sender, EventArgs e)
		{
			this.copyFields(listFields, FieldSelectorPresenter.CopyMode.block);
		}

		private void btnCopyAllList_Click(object sender, EventArgs e)
		{
			this.copyFields(listFields, FieldSelectorPresenter.CopyMode.list);
		}

		private void textTable_MouseDoubleClick(object sender, MouseEventArgs e)
		{
			Clipboard.SetText(this.textTable.Text); // trust the gui to do this.
		}

		private void checkprefix_CheckedChanged(object sender, EventArgs e)
		{
			this.fieldSelectorPresenter.SetPrefix(this.checkprefix.Checked);
		}

		private void textPrefix_TextChanged(object sender, EventArgs e)
		{
			this.fieldSelectorPresenter.SetPrefixString(textPrefix.Text);
		}

		private void copyFields(ListBox lb, FieldSelectorPresenter.CopyMode copyMode)
		{
			// new logic: if one selected copy all 
			// else copy the selected
			if (lb.SelectedIndices.Count <= 1)
				this.fieldSelectorPresenter.CopyFields(copyMode);
			else
				copySelected(copyMode);
		}


		private void copySelected(FieldSelectorPresenter.CopyMode copyMode)
		{
			List<String> selected = new List<string>();
			foreach (int idx in listFields.SelectedIndices)
				selected.Add(listFields.Items[idx].ToString());
			this.fieldSelectorPresenter.CopySelection(selected, copyMode);
		}

		// END: field selector stuff
		///////////////////////////////////////////////////////////

		///////////////////////////////////////////////////////////

		// Members
		// Global objects
		MruMenu mruMenu; // object for most recently used
		FieldSelectorPresenter fieldSelectorPresenter; // presenter layer for field selector.
		TabCollection tabs;		// tabbed window manager
		string initialConnectionTag = ""; // initialialisation parameter (quick launch connection)

		// state
		// field selector window state. Used when switching back to tables from
		// any other grid view. The tables view sets the field selector visible depending on this value.
		bool fieldPanelActive = false;
		bool holdFieldPanelActive = false; // works around knarly state-saving logic issues
		// Current driver for ui state
		string currentDriverName = "";
		// clipboard current format string. (Esoteric: normally blank)
		string clipTemplateString = "";
		// reference to active tab present
		MainPresenter currentPresenter;
		// reference to active gridview
		DbViewDataGridView currentGrid;
		// boot state: muck about with this as necessary
		enum Appstate { booting, closing, running }
		Appstate applicationState = Appstate.booting;
		bool gridEditErrorState = false; // for trapping odd cell edit error cases.

		// tab setting state
		enum TabMode { legacy, standard, tableindex, super }
		// start 'em with tab mode
		TabMode currentTabMode = TabMode.standard;

		bool FastFindActivated = false; // feature status unsure.

		// Map additional UI state to the presenters associated with each tab
		Dictionary<MainPresenter, PresenterGridState> gridStateMap = new Dictionary<MainPresenter, PresenterGridState>();
		// Originally for grid state connected with the presenter enlarged to hold other state
		class PresenterGridState
		{
			internal DataTable Table;
			internal int RowIndex;
			internal PresenterGridState(DataTable table, int row)
			{
				this.Table = table;
				this.RowIndex = row;
			}
			internal bool IsFieldSelectorVisible = false;
			internal string AdhocText = "";
			internal bool IsAdhocDisplayed = false;
			internal DataTable Results = null;
		}

		//////////////////////////////////////////////////////////////////////
		// Adhoc window processing
		//////////////////////////////////////////////////////////////////////
		// logic for shared use
		// Wrap getting, setting and querying the visibility of the adhoc window,
		private bool IsAdhocShown { get { return (this.GridSplitter.Panel2Collapsed == false); } }
		private void HideAdhoc()
		{
			this.GridSplitter.Panel2Collapsed = true;
			// toolbar
			this.tbrAdhoc.Pushed = false;
		}

		private void ShowAdhoc(bool setFocus)
		{
			this.GridSplitter.Panel2Collapsed = false;
			if (setFocus) textCommands.Focus();
			// toolbar
			this.tbrAdhoc.Pushed = true;
		}

		private void btnCloseAdhoc_Click(object sender, EventArgs e) { HideAdhoc(); }

		// Grab and let go of the Enter key when editing SQL
		private void toolBar_ButtonClick(object sender, System.Windows.Forms.ToolBarButtonClickEventArgs e) { OnAdHocToolbarButtonPress(e.Button.ImageIndex); }
		private void dgResults_MouseDown(object sender, MouseEventArgs e)
		{
			DataGridView dgv = sender as DataGridView;
			OnMouseDownInAdHocQueryGrid(dgv, e.X, e.Y, (e.Button == MouseButtons.Right));
		}
		private void textCommands_Enter(object sender, EventArgs e) { this.AcceptButton = null; }
		private void textCommands_Leave(object sender, EventArgs e) { this.AcceptButton = btnGo; }

		private void btnClear_Click(object sender, EventArgs e) { ClearAdhoc(); }


		// change size of source and result grid. Old interface now removed
		//private void btnToggle_Click(object sender, EventArgs e) { OnToggle(adhocSplitter); }
		//private void btnResize_Click(object sender, EventArgs e) { ResizeAdhocWindow(); }
		//private void btnExecute_Click(object sender, EventArgs e) { ExecuteCommand(); }
		private void textCommands_KeyPress(object sender, System.Windows.Forms.KeyPressEventArgs e) { }

		// Hijack Ctrl-E for Execute instead of centering
		private void textCommands_KeyUp(object sender, KeyEventArgs e) { CheckAndHandleCtrlE(e, true); }
		private void textCommands_KeyDown(object sender, KeyEventArgs e) { CheckAndHandleCtrlV(sender as RichTextBox, e); }

		private void OnMouseDownInAdHocQueryGrid(DataGridView resultGrid, int gridX, int gridY, bool isRightClick)
		{
			DataGridView.HitTestInfo hti = resultGrid.HitTest(gridX, gridY);
			// click in a cell 
			if (hti.Type == DataGridViewHitTestType.Cell)
			{
				// decide whether to reset the selection on right-click
				bool resetCurrent = true;  // assume yes
				// if the hit is in the selection then leave the selection intact
				foreach (DataGridViewCell cell in resultGrid.SelectedCells)
				{
					if (cell.ColumnIndex == hti.ColumnIndex && cell.RowIndex == hti.RowIndex)
					{
						resetCurrent = false;
						break;
					}
				}
				if (resetCurrent)
					resultGrid.CurrentCell = resultGrid.Rows[hti.RowIndex].Cells[hti.ColumnIndex];

				if (isRightClick)
				{
					DataRow dr;
					Helper.GetSelectedRow(resultGrid, out dr);
					// account for null datarow return (eg click in the new row)
					string data = (dr == null) ? "" : dr[hti.ColumnIndex].ToString().Trim();
					// copy all marked to clipboard, applying the template if any is set
					// check count of selections
					if (CheckCountAndConfirmIfLarge(resultGrid, 60000))
					{
						CopyCellDataToClipBoard(resultGrid.GetClipboardContent(), true);

						// below just provides a visual confirmation that the selection is made
						// get the selction background as the cell was selected above
						Color c = resultGrid.DefaultCellStyle.SelectionBackColor;
						// temporarily set the selection colour to red and update screen
						// additional queue: set colour depending on whether a template is active or not.
						resultGrid.DefaultCellStyle.SelectionBackColor = (this.clipTemplateString.Length > 0) ? Color.Plum : Color.Red;
						resultGrid.Refresh();
						// short delay so colour change is apparent to user
						System.Threading.Thread.Sleep(100);
						// restore
						resultGrid.DefaultCellStyle.SelectionBackColor = c;
						resultGrid.Refresh();
					}

					// new: (2011). If large text popup sourceform box
					if (data.Length > 192 || data.Contains("\n")) // MAGIC NUMBER!!!!
					{
						ShowSource("", Helper.FormatLongCellDataForDisplay(data), "");
					}

				}
			}
		}

		private bool CheckCountAndConfirmIfLarge(DataGridView resultGrid, int selectCellCountWarningLevel)
		{
			int selectedCellCount = resultGrid.SelectedCells.Count;
			if (selectedCellCount <= selectCellCountWarningLevel)
				return true; // OK to continue without an annoying message
			String question = String.Format("There are {0} cells selected for copy to the clipboard. "+
								"It may take some time.\n\n" +
								"Do you still wish to proceed?\n\n" +
								"(Hint: watch for the cell colour change to indicate "+
								"when the copy is complete.)", selectedCellCount);
			return (this.AskUser(question, "Proceed with clipboard operation?", false) == 1);
		}

		private void OnAdHocToolbarButtonPress(int ImageIndex)
		{
			string todoMessage = "";
			switch (ImageIndex)
			{
				case 19: HideAdhoc(); ; break;
				case 16: ExecuteCommand(); ; break;
				case 21: ClearAdhoc(); break;
				case 17: ; ResplitAdHoc(adhocSplitter, true); break;
				case 18: ; ResplitAdHoc(adhocSplitter, false); break;
				case 24: ; ExportGridToExcel(true); break;
			}
			if (todoMessage.Length > 0)
				MessageBox.Show(todoMessage, "Toolbar Todo");
		}


		private void CheckAndHandleCtrlV(RichTextBox tb, KeyEventArgs e)
		{
			// Add select all?
			// Problem is Ctrl-A is hijacked for the select all menu option
			// perhaps change functionality depending on what has focus?
			if ((Control.ModifierKeys & Keys.Control) == Keys.Control && e.KeyCode == Keys.E)
			{
				e.Handled = true;
			}
			// Thanks to http://stackoverflow.com/questions/2037827/c-sharp-rtb-paste-plain-text-without-colours-fonts
			if ((e.Control && e.KeyCode == Keys.V) || (e.Shift && e.KeyCode == Keys.Insert))
			{
				tb.Paste(DataFormats.GetFormat("Text"));
				e.Handled = true;
			}
		}

		// Hijack Ctrl-E for Execute instead of centering
		private void CheckAndHandleCtrlE(KeyEventArgs e, bool execute)
		{
			if ((Control.ModifierKeys & Keys.Control) == Keys.Control && e.KeyCode == Keys.E)
			{
				e.Handled = true;
				if (execute) ExecuteCommand(); // on KeyUp
			}
			if ((Control.ModifierKeys & Keys.Control) == Keys.Control && e.KeyCode == Keys.F4)
			{
				e.Handled = true;
				HideAdhoc();
			}
			this.currentPresenter.AdHocCommandTextChanged(this.textCommands.Text);
		}

		private void ExecuteCommand()
		{
			// work out whether this will return a data set
			string script = this.textCommands.SelectedText;
			// use everything if nothing is selected
			if (script.Length == 0)
				script = this.textCommands.Text;
			if (script.Length == 0)
				return; // do nothing if window is empty
			// 
			try
			{
				/*
					To deal with the query/command distinction require returned table to be decorated
					with extra info that contains the message
				*/
				SetAdhocToolbarStatus(false);
				this.textMessages.Text = "Working...\nClose the ad-hoc query window to prevent a very large result from displaying.";
				this.ShowAdHocMessages();
				this.Refresh();
				Command adhocCommand = new Command(Commands.Cmnd.Adhoc);
				ViewContext vc = new ViewContext(adhocCommand);
				vc.Command = adhocCommand;
				vc.FilterString = script; // hijack the filter string for the whole script
				// check connection info attached to the source.
				//DataTable dt = this.currentPresenter.Model.DataSource.BuildTable(adhocCommand, vc);
				//AdhocQueryComplete(dt);
				this.currentPresenter.RunThreadedAdhocQuery(adhocCommand, vc);
			}
			catch (Exception exc)
			{
				// be a little bit intelligent and helpful here.
				this.textMessages.Text = exc.Message;
				this.ShowAdHocMessages();
				this.toolStripStatusMessage.Text = "!Error";
				// fail state
				AdhocQueryComplete(null);
			}
		}

		private void SetAdhocToolbarStatus(bool state)
		{
			this.tbAdhocExecute.Enabled = state;
			this.tbAdhocClear.Enabled = state;
		}
		// Adhoc query thread completed callback
		public void AdhocQueryComplete(DataTable table)
		{
			// the adhoc window may be closed during the query by user action
			// taken to mean that the query result is no longer wanted.
			if (!this.IsAdhocShown)
			{
				// True it's finished as you cannot cancel
				this.textMessages.Text = "Command completed.";
				// if a table was returned
				if (table != null)
				{
					// just confirm that it was not taken any further.
					this.textMessages.Text += "\nResult not loaded into grid";
				}
				// tidy up the results and the status
				SetResults(null);
				// leave the tab on the message tab
				this.ShowAdHocMessages();
			}
			// On query completed and results wanted (ie the normal branch)
			else
			{
				// table is null if a command was executed. Normally there is a table to load into the grid
				if (table != null)
				{
					DataTableExtraInfoManager mgr = new DbView.DataTableExtraInfoManager(table);
					// This is possibly an error reporting mechanism apparently not used
					string message = mgr.GetExtraInfo("MESSAGE", "");
					// Build an appropriate messgage when finished. Note the grid will be displayed
					int rows = table.Rows.Count;
					this.textMessages.Text = "Command completed";
					this.textMessages.Text += string.Format("\n{0}", message);
					
					// Clear first added for render speed. Remove if it proves ugly
					this.dgResults.Visible = false; 
					SetResults(null);

					SetResults(table);
					// prevent edits unless user asks
					this.dgResults.ReadOnly = true;
					this.dgResults.Visible = true; // added for render speed. Remove if it proves ugly
				}
				// setting to either grid or message tab.
				if (table == null || table.Columns.Count == 0)
					this.ShowAdHocMessages();
				else
					this.ShowAdHocResults();
				// set focus back to the command window
				textCommands.Focus();		
			}
			// reactivate the go button
			SetAdhocToolbarStatus(true);
		}

		private void ShowAdHocMessages() { this.AdhocResultPanel.SelectedIndex = 0; }
		private void ShowAdHocResults() { this.AdhocResultPanel.SelectedIndex = 1; }

		private void ClearAdhoc()
		{
			// clear the SQL window
			this.textCommands.Text = "";
			this.currentPresenter.AdHocCommandTextChanged("");
			// clear messages
			this.textMessages.Text = "";
			// switch from results to messages tab
			this.ShowAdHocMessages();
			// clear results.
			SetResults(null);
		}

		void SetResults(DataTable dt)
		{
			this.dgResults.BindDataTable(dt, false);
			
			// not strictly seting results, but this control should be in synch with the display
			string statusMsg = ""; // clear if results is clear,
			if (dt != null)
				statusMsg = string.Format("Done: {0} rows", dt.Rows.Count);
			this.toolStripStatusMessage.Text = statusMsg;
		}

		DataTable GetResults()
		{
			// Navigate from the gridview control via it's datasource
			BindingSource bs = (BindingSource)dgResults.DataSource;
			if (bs == null) return null; // arrives here at odd times on start-up so abandon
			// to the actual table via the datasource of the binding source.
			DataTable dt = (DataTable)bs.DataSource;
			return dt;
		}

		private bool ReturnsData(String script)
		{
			// check for select
			if (script.ToUpper().IndexOf("SELECT ") == -1)
				return false;
			return true;
		}

		private void ResplitAdHoc(SplitContainer splitter, bool enlargeResults)
		{
			// window size
			int toggleFactor = 5;  // splits 4:1
			int newSetting = (enlargeResults) ? splitter.Size.Height / toggleFactor // assumes shrinking the edit window
											  : splitter.Size.Height * (toggleFactor - 1) / toggleFactor;
			splitter.SplitterDistance = newSetting;
		}

		private void ExportGridToExcel(bool allowCSV)
		{
			DataTable dt = this.GetResults();
			if (dt == null) return;

			// It's a guess as the marking may have changed since the command was executed.
			string script = this.textCommands.SelectedText;
			// use everything if nothing is selected
			if (script.Length == 0)
				script = this.textCommands.Text;

			ConnectionInfo ci = this.currentPresenter.Model.GetCurrentConnection();
			this.currentPresenter.UserRequestedExportToExcel(dt, "DbViewSharpQuery", ci, script, allowCSV);
		}

		// for when the display is autoupdating.
		private void timerRefresh_Tick(object sender, EventArgs e)
		{
			this.currentPresenter.UserRequestedRefresh(FilterText);
		}

		private void comboSearchPart_TextChanged(object sender, EventArgs e)
		{
			ComboBox cb = (sender as ComboBox);
			if (this.FastFindActivated)
			{
				// this directs the filter to be ignored if what is entered is less than 3 chars
				// unless it contains a wild card search.
				int minEffectiveLength = (cb.Text.Contains("%")) ? 0 : 2; 
				minEffectiveLength = 0; // don't like the effect this has so disabling
				this.currentGrid.ApplyRegexFilter(Helper.SqlToRegex(cb.Text, true), minEffectiveLength);
//				this.currentGrid.ApplyFilter(cb.Text);
				this.UpdateStatusBarFastFindCount();
			}
		}

		private void comboSearchPart_KeyDown(object sender, KeyEventArgs e)
		{
			ComboBox cb = sender as ComboBox;
			bool isOpen = cb.DroppedDown;
			if (e.KeyCode == Keys.Escape)
			{
			    // if something entered in the text box clear it
			    if (comboSearchPart.Text.Length > 0)
			    {
			        comboSearchPart.Text = "";
			    }
			    // otherwise back 
			    else
			        this.currentPresenter.UserRequestedBack();
			    e.Handled = true;
			}
			//
			// Below is for when the keys entered affect the grid
			//
			//if (e.KeyCode == Keys.Escape)
			//{
			//    // if something entered in the text box clear it
			//    if (comboSearchPart.Text.Length > 0)
			//    {
			//        comboSearchPart.Text = "";
			//    }
			//    // otherwise back 
			//    else
			//        this.currentPresenter.UserRequestedBack();
			//    e.Handled = true;
			//}
			//if (e.KeyCode == Keys.Down && !isOpen && !e.Alt && !string.IsNullOrEmpty(this.comboSearchPart.Text))
			//{
			//    // this for highlight don't hide
			//    // move the rowindex on 1 unless at the end
			//    if (false)
			//    {
			//        if (this.currentGrid.CurrentCell.OwningRow.Index < this.currentGrid.RowCount - 1)
			//            this.currentGrid.CurrentCell = this.currentGrid[this.currentGrid.CurrentCell.OwningColumn.Index, this.currentGrid.CurrentCell.OwningRow.Index + 1];
			//    }
			//    // else for when only matching rows are shown
			//    else
			//    {
			//        if (this.currentGrid.CurrentCell == null)
			//        {
			//            foreach (DataGridViewRow rw in this.currentGrid.Rows)
			//                if (rw.Visible == true)
			//                {
			//                    this.currentGrid.CurrentCell = rw.Cells[0];
			//                    break;
			//                }
			//        }
			//    }
			//    this.currentGrid.Focus();
			//    e.Handled = true;
			//}
			//if (e.KeyCode == Keys.Up && !isOpen && !e.Alt && !string.IsNullOrEmpty(this.comboSearchPart.Text))
			//{
			//    // move the rowindex on 1 unless at the end
			//    if (this.currentGrid.CurrentCell != null)
			//        if (this.currentGrid.CurrentCell.OwningRow.Index > 0)
			//            this.currentGrid.CurrentCell = this.currentGrid[this.currentGrid.CurrentCell.OwningColumn.Index, this.currentGrid.CurrentCell.OwningRow.Index - 1];
			//    this.currentGrid.Focus();
			//    e.Handled = true;
			//}
			//else if (e.KeyCode == System.Windows.Forms.Keys.F3)
			//{
			//    this.currentGrid.Focus();
			//    this.currentGrid.MoveCurrentSelection(e.Shift ? -1 : 1);
			//    e.Handled = true;
			//}
		}
	}
}
